This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

spotify <- read.csv("Popular_Spotify_Songs.csv")
head(spotify)

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Cmd+Option+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Cmd+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

spotify <- read.csv("Popular_Spotify_Songs.csv")
head(spotify)
#spotify[cols_to_convert] <- lapply(spotify[cols_to_convert], function(x) as.numeric(as.character(x)))

spotify$streams <- as.numeric(spotify$streams)
G2;H2;Warningh: NAs introduced by coerciong
spotify$in_deezer_playlists = as.numeric(spotify$in_deezer_playlists)
G2;H2;Warningh: NAs introduced by coerciong
spotify$in_shazam_charts = as.numeric(spotify$in_shazam_charts)
G2;H2;Warningh: NAs introduced by coerciong
str(spotify[, 3:14])
'data.frame':   953 obs. of  12 variables:
 $ artist_count        : int  2 1 1 1 1 2 2 1 1 2 ...
 $ released_year       : int  2023 2023 2023 2019 2023 2023 2023 2023 2023 2023 ...
 $ released_month      : int  7 3 6 8 5 6 3 7 5 3 ...
 $ released_day        : int  14 23 30 23 18 1 16 7 15 17 ...
 $ in_spotify_playlists: int  553 1474 1397 7858 3133 2186 3090 714 1096 2953 ...
 $ in_spotify_charts   : int  147 48 113 100 50 91 50 43 83 44 ...
 $ streams             : num  1.41e+08 1.34e+08 1.40e+08 8.01e+08 3.03e+08 ...
 $ in_apple_playlists  : int  43 48 94 116 84 67 34 25 60 49 ...
 $ in_apple_charts     : int  263 126 207 207 133 213 222 89 210 110 ...
 $ in_deezer_playlists : num  45 58 91 125 87 88 43 30 48 66 ...
 $ in_deezer_charts    : int  10 14 14 12 15 17 13 13 11 13 ...
 $ in_shazam_charts    : num  826 382 949 548 425 946 418 194 953 339 ...
pairs(spotify[, 3:14], main = "Linear Relationships Between Metrics")

# Select only the relevant columns
selected_data <- spotify[, c("streams", "in_spotify_playlists", "in_deezer_playlists", "in_apple_playlists")]

# Create the pairs plot
pairs(selected_data, main = "Pairs Plot of Playlist Counts and Streams")

colSums(is.na(spotify))
          track_name       artist.s._name         artist_count        released_year       released_month         released_day in_spotify_playlists 
                   0                    0                    0                    0                    0                    0                    0 
   in_spotify_charts              streams   in_apple_playlists      in_apple_charts  in_deezer_playlists     in_deezer_charts     in_shazam_charts 
                   0                    1                    0                    0                   79                    0                   57 
                 bpm                  key                 mode       danceability_.            valence_.             energy_.       acousticness_. 
                   0                    0                    0                    0                    0                    0                    0 
  instrumentalness_.           liveness_.        speechiness_. 
                   0                    0                    0 
dim(spotify)
[1] 953  24
library(ggplot2)

ggplot(spotify, aes(x = released_year)) +
  geom_histogram(binwidth = 1, fill = "skyblue", color = "white") +
  labs(title = "Distribution of Streams", x = names(spotify$released_year))


plot(density(spotify$released_year, na.rm = TRUE), main = "Density Plot of Released Year", xlab = "Released Year", col = "blue", lwd = 2)

View(spotify)
# Basic scatter plot with color based on 'mode'
ggplot(spotify, aes(x = streams, y = in_spotify_playlists, color = mode)) +
  geom_point() +
  labs(title = "Streams vs Playlist Metrics by Mode",
       x = "Streams",
       y = "Number in Spotify Playlists") +
  theme_minimal()

library(shiny)
library(ggplot2)
# UI
ui <- fluidPage(
  titlePanel("Streams vs Spotify Playlists by Mode"),
  sidebarLayout(
    sidebarPanel(
      checkboxGroupInput("mode_select", "Select Mode(s):",
                         choices = unique(spotify$mode),
                         selected = unique(spotify$mode))
    ),
    mainPanel(
      plotOutput("scatterPlot")
    )
  )
)

# Server
server <- function(input, output) {
  output$scatterPlot <- renderPlot({
    filtered_data <- spotify[spotify$mode %in% input$mode_select, ]

    ggplot(filtered_data, aes(x = streams, y = in_spotify_playlists, color = mode)) +
      geom_point() +
      labs(
        title = "Streams vs Playlist Metrics by Mode",
        x = "Streams",
        y = "Number in Spotify Playlists"
      ) +
      theme_minimal()
  })
}

# Run the app
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:4945
g
library(dplyr)
# Create a combined label of Song + Artist
spotify <- spotify %>%
  mutate(song.artist = paste(track_name, "-", artist.s._name))
view(spotify)
G1;H1;Errorh in view(spotify) : could not find function "view"
Error during wrapup: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
yearly_top_song <- spotify %>%
  group_by(released_year) %>%
  slice_max(order_by = streams, n = 1, with_ties = TRUE) %>%
  ungroup()

# Step 1: Get the top 10 songs by total streams
top10_yearly <- yearly_top_song %>% 
  arrange(desc(streams)) %>% 
  slice(1:10)

top10_yearly

# Convert song_artist to factor with levels ordered by Streams
top10_yearly <- top10_yearly %>%
  arrange(desc(streams)) %>%
  mutate(song.artist = factor(song.artist, levels = unique(song.artist)))
top10_yearly
ggplot(top10_yearly, aes(x = released_year, y = streams, fill = factor(song.artist))) +
  geom_bar(stat = "identity") +
  labs(title = "Top Streamed Songs per Year",
       x = "Track (Song - Artist)",
       y = "Number of Streams",
       fill = "Year") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

ggplot(top10_yearly, aes(x = factor(released_year), y = streams, fill = song.artist)) +
  geom_bar(stat = "identity") +
  labs(title = "Top Streamed Song For Each Released Year: Top 10 Ranking Overall",
       x = "Year",
       y = "Streams",
       fill = "Song - Artist") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

yearly_top_songs <- spotify %>%
  group_by(released_year) %>%
  slice_max(order_by = streams, n = 5, with_ties = TRUE) %>%
  ungroup()

View(yearly_top_songs)
# Filter for 2023 top 5 songs from your previously filtered data
top_2023 <- yearly_top_songs %>%
  filter(released_year == 2023)

# Create the bar chart
ggplot(top_2023, aes(x = reorder(track_name, -streams), y = streams, fill = song.artist)) +
  geom_bar(stat = "identity") +
  labs(title = "Top 5 Streamed Songs in 2023",
       x = "Song",
       y = "Streams",
       fill = "Song - Artist") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Check how many rows are in the dataset for 2022
dim(yearly_top_songs[yearly_top_songs$released_year == 2022, ])
[1]  5 25
# Check for NA or invalid values in 2022
summary(yearly_top_songs[yearly_top_songs$released_year == 2022, ])
  track_name        artist.s._name      artist_count released_year  released_month  released_day  in_spotify_playlists in_spotify_charts
 Length:5           Length:5           Min.   :1.0   Min.   :2022   Min.   :3.0    Min.   : 6.0   Min.   : 8506        Min.   : 42.0    
 Class :character   Class :character   1st Qu.:1.0   1st Qu.:2022   1st Qu.:5.0    1st Qu.: 6.0   1st Qu.: 8576        1st Qu.: 42.0    
 Mode  :character   Mode  :character   Median :2.0   Median :2022   Median :5.0    Median : 6.0   Median : 8870        Median : 43.0    
                                       Mean   :1.6   Mean   :2022   Mean   :5.8    Mean   :14.2   Mean   :11713        Mean   : 60.4    
                                       3rd Qu.:2.0   3rd Qu.:2022   3rd Qu.:7.0    3rd Qu.:22.0   3rd Qu.: 9037        3rd Qu.: 45.0    
                                       Max.   :2.0   Max.   :2022   Max.   :9.0    Max.   :31.0   Max.   :23575        Max.   :130.0    
                                                                                                                                        
    streams          in_apple_playlists in_apple_charts in_deezer_playlists in_deezer_charts in_shazam_charts      bpm            key           
 Min.   :1.231e+09   Min.   : 94.0      Min.   : 65.0   Min.   :139.0       Min.   :14.0     Min.   : 49.0    Min.   : 92.0   Length:5          
 1st Qu.:1.264e+09   1st Qu.:104.0      1st Qu.:108.0   1st Qu.:141.0       1st Qu.:14.0     1st Qu.:127.8    1st Qu.:107.0   Class :character  
 Median :1.357e+09   Median :124.0      Median :120.0   Median :164.0       Median :26.0     Median :160.0    Median :128.0   Mode  :character  
 Mean   :1.561e+09   Mean   :188.2      Mean   :124.8   Mean   :327.6       Mean   :25.2     Mean   :136.2    Mean   :126.4                     
 3rd Qu.:1.441e+09   3rd Qu.:216.0      3rd Qu.:133.0   3rd Qu.:331.0       3rd Qu.:26.0     3rd Qu.:168.5    3rd Qu.:131.0                     
 Max.   :2.513e+09   Max.   :403.0      Max.   :198.0   Max.   :863.0       Max.   :46.0     Max.   :176.0    Max.   :174.0                     
                                                                                             NA's   :1                                          
     mode           danceability_.   valence_.       energy_.    acousticness_. instrumentalness_.   liveness_.   speechiness_.  song.artist       
 Length:5           Min.   :52.0   Min.   :19.0   Min.   :47.0   Min.   : 1     Min.   :0.0        Min.   : 9.0   Min.   : 4.0   Length:5          
 Class :character   1st Qu.:62.0   1st Qu.:24.0   1st Qu.:71.0   1st Qu.: 1     1st Qu.:0.0        1st Qu.:13.0   1st Qu.: 6.0   Class :character  
 Mode  :character   Median :65.0   Median :43.0   Median :72.0   Median : 9     Median :0.0        Median :23.0   Median : 8.0   Mode  :character  
                    Mean   :68.2   Mean   :41.4   Mean   :68.2   Mean   :11     Mean   :0.6        Mean   :20.6   Mean   :10.4                     
                    3rd Qu.:71.0   3rd Qu.:55.0   3rd Qu.:73.0   3rd Qu.:10     3rd Qu.:0.0        3rd Qu.:27.0   3rd Qu.: 9.0                     
                    Max.   :91.0   Max.   :66.0   Max.   :78.0   Max.   :34     Max.   :3.0        Max.   :31.0   Max.   :25.0                     
                                                                                                                                                   
# Alternatively, print it to inspect
print(yearly_top_songs[yearly_top_songs$released_year == 2022, ])
# Look at 2022 data closely
spotify %>%
  filter(released_year == 2022) %>%
  select(track_name, artist.s._name, streams) %>%
  glimpse()
Rows: 402
Columns: 3
$ track_name     <chr> "As It Was", "Kill Bill", "Calm Down (with Selena Gomez)", "Creepin'", "Anti-Hero", "I'm Good (Blue)", "I Ain't Worried", "La …
$ artist.s._name <chr> "Harry Styles", "SZA", "R��ma, Selena G", "The Weeknd, 21 Savage, Metro Boomin", "Taylor Swift", "Bebe Rexha, David Guetta", "…
$ streams        <dbl> 2513188493, 1163093654, 899183384, 843957510, 999748277, 1109433169, 1085685420, 1214083358, 720434240, 674072710, 404562836, …
# Count unique songs to see if there's a tie issue
yearly_top_songs %>%
  filter(released_year == 2022) %>%
  count(track_name)

# Check for NAs in streams or grouping variables
yearly_top_songs %>%
  filter(released_year == 2022) %>%
  summarise(
    missing_streams = sum(is.na(streams)),
    missing_track = sum(is.na(track_name)),
    missing_artist = sum(is.na(song.artist)))
# Shiny app to view top streamed songs by year with a toggle
library(shiny)
library(dplyr)
library(ggplot2)

# UI
ui <- fluidPage(
  titlePanel("Top Streamed Songs by Year"),
  sidebarLayout(
    sidebarPanel(
      selectInput("year", "Select Year:", choices = sort(unique(yearly_top_songs$released_year)))
    ),
    mainPanel(
      plotOutput("topSongsPlot")
    )
  )
)

# Server
server <- function(input, output, session) {
  
  output$topSongsPlot <- renderPlot({
    selected_year_data <- yearly_top_songs %>% 
      filter(released_year == input$year)

    # Ensure no invalid characters or encoding issues in track names
    selected_year_data$track_name <- iconv(selected_year_data$track_name, from = "UTF-8", to = "UTF-8", sub = "*")

    ggplot(selected_year_data, aes(x = reorder(track_name, -streams), y = streams, fill = song.artist)) +
      geom_bar(stat = "identity") +
      labs(title = paste("Top 5 Streamed Songs in", input$year),
           x = "Song",
           y = "Number of Streams",
           fill = "Song & Artist") +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
}

# Run app
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:4945
gG1;H1;Error during wrapuph: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart

gG1;H1;Error during wrapuph: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
view(top_2023)
G1;H1;Errorh in view(top_2023) : could not find function "view"
Error during wrapup: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
names(top_2023)
 [1] "track_name"           "artist.s._name"       "artist_count"         "released_year"        "released_month"       "released_day"        
 [7] "in_spotify_playlists" "in_spotify_charts"    "streams"              "in_apple_playlists"   "in_apple_charts"      "in_deezer_playlists" 
[13] "in_deezer_charts"     "in_shazam_charts"     "bpm"                  "key"                  "mode"                 "danceability_."      
[19] "valence_."            "energy_."             "acousticness_."       "instrumentalness_."   "liveness_."           "speechiness_."       
[25] "song.artist"         
# Load packages explicitly
library(dplyr)
library(tidyr)

# Now explicitly call dplyr::select() to avoid masking
top_2023_features <- top_2023 %>%
  dplyr::select(
    song.artist,
    bpm,
    `danceability_.`,
    `speechiness_.`,
    `energy_.`,
    `acousticness_.`
  ) %>%
  pivot_longer(cols = -song.artist, names_to = "feature", values_to = "value")
# Step 2: Create circular barplot
ggplot(top_2023_features, aes(x = feature, y = value, fill = song.artist)) +
  geom_bar(stat = "identity", position = "dodge") +
  coord_polar() +
  labs(title = "Audio Feature Metrics for Top 5 Songs in 2023",
       x = "",
       y = "",
       fill = "Song - Artist") +
  theme_minimal() +
  theme(axis.text.y = element_blank(),
        axis.ticks = element_blank(),
        panel.grid = element_blank(),
        axis.text.x = element_text(size = 12, face = "bold"))

yearly_top3_songs <- spotify %>%
  group_by(released_year) %>%
  slice_max(order_by = streams, n = 3, with_ties = TRUE) %>%
  ungroup()

View(yearly_top3_songs)
# Filter for 2023 top 3 songs from your previously filtered data
top3_2023 <- yearly_top3_songs %>%
  filter(released_year == 2023)

head(top3_2023)
# Now explicitly call dplyr::select() to avoid masking
top3_2023_features <- top3_2023 %>%
  dplyr::select(
    song.artist,
    bpm,
    `danceability_.`,
    `speechiness_.`,
    `energy_.`,
    `acousticness_.`
  ) %>%
  pivot_longer(cols = -song.artist, names_to = "feature", values_to = "value")
head(top3_2023)
# Step 2: Create circular barplot
ggplot(top3_2023_features, aes(x = feature, y = value, fill = song.artist)) +
  geom_bar(stat = "identity", position = "dodge") +
  coord_polar() +
  labs(title = "Audio Feature Metrics for Top 5 Songs in 2023",
       x = "",
       y = "",
       fill = "Track Name") +
  theme_minimal() +
  theme(axis.text.y = element_blank(),
        axis.ticks = element_blank(),
        panel.grid = element_blank(),
        axis.text.x = element_text(size = 12, face = "bold"))

# Load clean libraries (force reloading if needed)
library(dplyr)
library(tibble)
library(tidyr)
library(fmsb)
G2;H2;Warningh: package ‘fmsb’ was built under R version 4.3.3g
G3;Registered S3 methods overwritten by 'fmsb':
  method    from
  print.roc pROC
  plot.roc  pROC
g
library(scales)
G2;H2;Warningh: package ‘scales’ was built under R version 4.3.3g
# Clean column names using backticks explicitly
top_2023_clean <- top_2023 %>%
  dplyr::rename(
    danceability = `danceability_.`,
    speechiness = `speechiness_.`,
    energy = `energy_.`,
    acousticness = `acousticness_.`
  )

# Select only relevant columns
radar_data <- dplyr::select(top_2023_clean, song.artist, bpm, danceability, speechiness, energy, acousticness)

# Normalize the metrics to range [0, 1]
radar_data_norm <- radar_data %>%
  mutate(across(where(is.numeric) & !song.artist, ~ scales::rescale(.x, to = c(0, 1))))
# Create max and min rows for required radar structure
max_min <- data.frame(
  bpm = 1, danceability = 1, speechiness = 1, energy = 1, acousticness = 1,
  row.names = c("Max")
) %>%
  bind_rows(data.frame(
    bpm = 0, danceability = 0, speechiness = 0, energy = 0, acousticness = 0,
    row.names = c("Min")
  ))

# Add the song data with rownames as song titles
radar_matrix <- bind_rows(
  max_min,
  radar_data_norm %>% column_to_rownames("song.artist")
)
# Assign colors per song
colors_border <- rainbow(nrow(radar_matrix) - 2)
colors_in <- adjustcolor(colors_border, alpha.f = 0.25)

# Plot
fmsb::radarchart(
  radar_matrix,
  axistype = 1,
  pcol = colors_border,
  pfcol = colors_in,
  plwd = 2,
  plty = 1,
  cglcol = "grey",
  cglty = 1,
  axislabcol = "grey",
  caxislabels = seq(0, 1, 0.2),
  cglwd = 0.8,
  vlcex = 0.9,
  title = "Top 5 Songs in 2023 — Audio Features Radar Chart"
)

legend(
  "topright",
  legend = rownames(radar_matrix)[-c(1, 2)],
  bty = "n",
  pch = 20,
  col = colors_border,
  text.col = "black",
  cex = 0.8
)

# Clean column names using backticks explicitly
top3_2023_clean <- top3_2023 %>%
  dplyr::rename(
    danceability = `danceability_.`,
    speechiness = `speechiness_.`,
    energy = `energy_.`,
    acousticness = `acousticness_.`
  )

# Select only relevant columns
radar_data3 <- dplyr::select(top3_2023_clean, song.artist, bpm, danceability, speechiness, energy, acousticness)

# Normalize the metrics to range [0, 1]
radar_data_norm3 <- radar_data3 %>%
  mutate(across(where(is.numeric) & !song.artist, ~ scales::rescale(.x, to = c(0, 1))))
# Create max and min rows for required radar structure
max_min3 <- data.frame(
  bpm = 1, danceability = 1, speechiness = 1, energy = 1, acousticness = 1,
  row.names = c("Max")
) %>%
  bind_rows(data.frame(
    bpm = 0, danceability = 0, speechiness = 0, energy = 0, acousticness = 0,
    row.names = c("Min")
  ))

# Add the song data with rownames as song titles
radar_matrix3 <- bind_rows(
  max_min3,
  radar_data_norm3 %>% column_to_rownames("song.artist")
)
# Assign colors per song
colors_border <- rainbow(nrow(radar_matrix) - 2)
colors_in <- adjustcolor(colors_border, alpha.f = 0.25)

# Plot
fmsb::radarchart(
  radar_matrix3,
  axistype = 1,
  pcol = colors_border,
  pfcol = colors_in,
  plwd = 2,
  plty = 1,
  cglcol = "grey",
  cglty = 1,
  axislabcol = "grey",
  caxislabels = seq(0, 1, 0.2),
  cglwd = 0.8,
  vlcex = 0.9,
  title = "Top 3 Songs in 2023 — Audio Features Radar Chart"
)

legend(
  "topright",
  legend = rownames(radar_matrix3)[-c(1, 2)],
  bty = "n",
  pch = 20,
  col = colors_border,
  text.col = "black",
  cex = 0.8,
  inset = c(-0.1, 0) # Moves the legend to the right (negative x-inset)
)

library(dplyr)
library(tidyr)
library(fmsb)
library(scales)
library(shiny)
# UI ----
ui <- fluidPage(
  titlePanel("Radar Chart of Top 3 Spotify Songs by Year"),
  
  sidebarLayout(
    sidebarPanel(
      selectInput("selected_year", "Choose a Year:",
                  choices = NULL)
    ),
    
    mainPanel(
      plotOutput("radarPlot")
    )
  )
)

# Server ----
server <- function(input, output, session) {
  
  # Populate dropdown with available years
  observe({
    updateSelectInput(session, "selected_year",
                      choices = sort(unique(spotify$released_year), decreasing = TRUE),
                      selected = max(spotify$released_year, na.rm = TRUE))
  })
  
  # Reactive: create radar matrix for selected year
  radar_matrix3 <- reactive({
    req(input$selected_year)
    
    # Top 3 songs for selected year
    top3_year <- spotify %>%
      filter(released_year == input$selected_year) %>%
      slice_max(order_by = streams, n = 3, with_ties = FALSE) %>%
      dplyr::rename(
        danceability = `danceability_.`,
        speechiness = `speechiness_.`,
        energy = `energy_.`,
        acousticness = `acousticness_.`
      ) %>%
      dplyr::select(song.artist, bpm, danceability, speechiness, energy, acousticness)
    
    # Normalize numeric columns to 0–1
    radar_data_norm3 <- top3_year %>%
      mutate(across(where(is.numeric), ~ scales::rescale(.x, to = c(0, 1))))
    
    if (nrow(radar_data_norm3) < 1) return(NULL)
    
    # Create max/min rows
    max_min3 <- data.frame(
      bpm = 1, danceability = 1, speechiness = 1, energy = 1, acousticness = 1,
      row.names = c("Max")
    ) %>%
      bind_rows(data.frame(
        bpm = 0, danceability = 0, speechiness = 0, energy = 0, acousticness = 0,
        row.names = c("Min")
      ))
    
    # Bind normalized data, set rownames to song.artist
    final_matrix <- bind_rows(
      max_min3,
      radar_data_norm3 %>% column_to_rownames("song.artist")
    )
    
    return(final_matrix)
  })
  
  # Plot output ----
output$radarPlot <- renderPlot({
  matrix <- radar_matrix3()
  req(matrix)

  # Colors
  colors_border <- rainbow(nrow(matrix) - 2)
  colors_in <- adjustcolor(colors_border, alpha.f = 0.25)

  # Set up a two-row layout: chart on top, legend below
  layout(matrix(c(1, 2), nrow = 2), heights = c(4, 1))  # Top: 4x height, Bottom: 1x height

  # Top: Radar Chart
  par(mar = c(2, 2, 4, 2))  # reasonable margins
  fmsb::radarchart(
    matrix,
    axistype = 1,
    pcol = colors_border,
    pfcol = colors_in,
    plwd = 2,
    plty = 1,
    cglcol = "grey",
    cglty = 1,
    axislabcol = "grey",
    caxislabels = seq(0, 1, 0.2),
    cglwd = 0.8,
    vlcex = 0.9,
    title = paste("Top 3 Songs in", input$selected_year, "— Audio Features Radar Chart")
  )

  # Bottom: Legend
  par(mar = c(0, 0, 0, 0))  # no margins
  plot.new()
  legend(
    "center",
    legend = rownames(matrix)[-c(1, 2)],
    bty = "n",
    pch = 20,
    col = colors_border,
    text.col = "black",
    cex = 0.9,
    ncol = 1,  # You can change to 2+ if you want columns
    xpd = TRUE
  )
})
}
# Run the app ----
shinyApp(ui, server)
G3;
Listening on http://127.0.0.1:6557
g
# Select only numeric columns
numeric_cols <- spotify %>%
  select(where(is.numeric))

numeric_cols
# Calculate correlation of all numeric columns with 'streams'
correlations <- cor(numeric_cols, use = "complete.obs")
correlations
                     artist_count released_year released_month released_day in_spotify_playlists in_spotify_charts       streams in_apple_playlists
artist_count          1.000000000   0.061445644    0.009720347 -0.044766245        -0.0746868039      -0.002421656 -0.1090468634       -0.008712241
released_year         0.061445644   1.000000000    0.031372926  0.160042169        -0.3305741123       0.100988420 -0.1483509269       -0.155648773
released_month        0.009720347   0.031372926    1.000000000 -0.015820232        -0.0187633639      -0.031526077  0.0413240023        0.007380967
released_day         -0.044766245   0.160042169   -0.015820232  1.000000000        -0.0320967227       0.042203010  0.0410748362        0.028622345
in_spotify_playlists -0.074686804  -0.330574112   -0.018763364 -0.032096723         1.0000000000       0.173307807  0.7650951338        0.709922084
in_spotify_charts    -0.002421656   0.100988420   -0.031526077  0.042203010         0.1733078066       1.000000000  0.2454749140        0.213322335
streams              -0.109046863  -0.148350927    0.041324002  0.041074836         0.7650951338       0.245474914  1.0000000000        0.663657168
in_apple_playlists   -0.008712241  -0.155648773    0.007380967  0.028622345         0.7099220838       0.213322335  0.6636571679        1.000000000
in_apple_charts      -0.079655066   0.007650642   -0.010603129  0.009855360         0.2087053786       0.565321488  0.2508103705        0.322358302
in_deezer_playlists  -0.073406225  -0.265234545   -0.035433532 -0.041980554         0.7880546875       0.151785663  0.7185929567        0.645914528
in_deezer_charts      0.022218537   0.103287112   -0.001921194  0.063555630         0.1952907401       0.558419963  0.2594696320        0.409688235
in_shazam_charts     -0.031812269   0.054492378   -0.090799317  0.040728906         0.1111503800       0.594678886  0.0587456970        0.187401561
bpm                  -0.067047448  -0.041957657   -0.051936323 -0.048020996         0.0260085534       0.028010413  0.0327164251        0.044415122
danceability_.        0.209581804   0.192054100   -0.034978955  0.076211130        -0.1066197808       0.075249362 -0.0754316227        0.011504320
valence_.             0.120784211  -0.064812792   -0.118074232  0.071279071        -0.0552336199       0.056602171 -0.0584550791        0.053187299
energy_.              0.149966302   0.130105474   -0.081977712  0.064572106        -0.0494256700       0.104328458 -0.0496657926        0.074416649
acousticness_.       -0.101620287  -0.169751059    0.039266560 -0.010279631         0.0001543819      -0.078095007  0.0013286969       -0.088265650
instrumentalness_.   -0.052814944  -0.014754771    0.031122232  0.007126726         0.0121080272      -0.012565007 -0.0009670221       -0.045488723
liveness_.            0.041035230   0.007441171   -0.017825352  0.002619619        -0.0339739648      -0.039153639 -0.0387277529       -0.046255149
speechiness_.         0.117955768   0.126711891    0.030599526 -0.017347379        -0.0719087372      -0.086192083 -0.0907281501       -0.101941835
                     in_apple_charts in_deezer_playlists in_deezer_charts in_shazam_charts           bpm danceability_.    valence_.     energy_.
artist_count            -0.079655066        -0.073406225      0.022218537     -0.031812269 -0.0670474482    0.209581804  0.120784211  0.149966302
released_year            0.007650642        -0.265234545      0.103287112      0.054492378 -0.0419576571    0.192054100 -0.064812792  0.130105474
released_month          -0.010603129        -0.035433532     -0.001921194     -0.090799317 -0.0519363227   -0.034978955 -0.118074232 -0.081977712
released_day             0.009855360        -0.041980554      0.063555630      0.040728906 -0.0480209963    0.076211130  0.071279071  0.064572106
in_spotify_playlists     0.208705379         0.788054688      0.195290740      0.111150380  0.0260085534   -0.106619781 -0.055233620 -0.049425670
in_spotify_charts        0.565321488         0.151785663      0.558419963      0.594678886  0.0280104129    0.075249362  0.056602171  0.104328458
streams                  0.250810371         0.718592957      0.259469632      0.058745697  0.0327164251   -0.075431623 -0.058455079 -0.049665793
in_apple_playlists       0.322358302         0.645914528      0.409688235      0.187401561  0.0444151222    0.011504320  0.053187299  0.074416649
in_apple_charts          1.000000000         0.198692411      0.356675982      0.443346418  0.0512089175   -0.003976097  0.061427394  0.153590558
in_deezer_playlists      0.198692411         1.000000000      0.218281108      0.135919298  0.0453831408   -0.104850821 -0.025849620 -0.028605485
in_deezer_charts         0.356675982         0.218281108      1.000000000      0.374829138  0.0370517105    0.087187954  0.075155386  0.108571701
in_shazam_charts         0.443346418         0.135919298      0.374829138      1.000000000  0.0891578410   -0.010179394 -0.003080391  0.095095549
bpm                      0.051208918         0.045383141      0.037051710      0.089157841  1.0000000000   -0.140710959  0.050484657  0.003536259
danceability_.          -0.003976097        -0.104850821      0.087187954     -0.010179394 -0.1407109592    1.000000000  0.390335848  0.186243358
valence_.                0.061427394        -0.025849620      0.075155386     -0.003080391  0.0504846571    0.390335848  1.000000000  0.354253808
energy_.                 0.153590558        -0.028605485      0.108571701      0.095095549  0.0035362587    0.186243358  0.354253808  1.000000000
acousticness_.          -0.105083100         0.028837909     -0.043917700     -0.071673565 -0.0020473755   -0.239007880 -0.068070884 -0.554771840
instrumentalness_.      -0.010658818         0.021617457     -0.002299823     -0.015732282 -0.0009552758   -0.098154216 -0.136058212 -0.032914831
liveness_.              -0.001551996        -0.005142997      0.002914949     -0.045209630  0.0005645641   -0.093272303  0.016319569  0.120967010
speechiness_.           -0.157645853        -0.108361699     -0.073955127     -0.081685578  0.0247134810    0.173420342  0.036580343 -0.017125796
                     acousticness_. instrumentalness_.    liveness_. speechiness_.
artist_count          -0.1016202867      -0.0528149443  0.0410352297    0.11795577
released_year         -0.1697510593      -0.0147547713  0.0074411712    0.12671189
released_month         0.0392665600       0.0311222324 -0.0178253521    0.03059953
released_day          -0.0102796313       0.0071267258  0.0026196188   -0.01734738
in_spotify_playlists   0.0001543819       0.0121080272 -0.0339739648   -0.07190874
in_spotify_charts     -0.0780950070      -0.0125650073 -0.0391536392   -0.08619208
streams                0.0013286969      -0.0009670221 -0.0387277529   -0.09072815
in_apple_playlists    -0.0882656498      -0.0454887232 -0.0462551494   -0.10194183
in_apple_charts       -0.1050831002      -0.0106588177 -0.0015519961   -0.15764585
in_deezer_playlists    0.0288379089       0.0216174566 -0.0051429975   -0.10836170
in_deezer_charts      -0.0439176997      -0.0022998235  0.0029149486   -0.07395513
in_shazam_charts      -0.0716735649      -0.0157322822 -0.0452096305   -0.08168558
bpm                   -0.0020473755      -0.0009552758  0.0005645641    0.02471348
danceability_.        -0.2390078796      -0.0981542162 -0.0932723026    0.17342034
valence_.             -0.0680708838      -0.1360582123  0.0163195694    0.03658034
energy_.              -0.5547718398      -0.0329148310  0.1209670100   -0.01712580
acousticness_.         1.0000000000       0.0332206982 -0.0406689579   -0.02387702
instrumentalness_.     0.0332206982       1.0000000000 -0.0488636800   -0.08664221
liveness_.            -0.0406689579      -0.0488636800  1.0000000000   -0.04518074
speechiness_.         -0.0238770164      -0.0866422067 -0.0451807367    1.00000000
# Sort and view
sort(cor_with_streams, decreasing = TRUE)
             streams in_spotify_playlists  in_deezer_playlists   in_apple_playlists     in_deezer_charts      in_apple_charts    in_spotify_charts 
        1.0000000000         0.7650951338         0.7185929567         0.6636571679         0.2594696320         0.2508103705         0.2454749140 
    in_shazam_charts       released_month         released_day                  bpm       acousticness_.   instrumentalness_.           liveness_. 
        0.0587456970         0.0413240023         0.0410748362         0.0327164251         0.0013286969        -0.0009670221        -0.0387277529 
            energy_.            valence_.       danceability_.        speechiness_.         artist_count        released_year 
       -0.0496657926        -0.0584550791        -0.0754316227        -0.0907281501        -0.1090468634        -0.1483509269 
model <- lm(streams ~ in_spotify_playlists + in_deezer_playlists + in_apple_playlists + danceability_. + energy_. + valence_., data = spotify)
summary(model)

Call:
lm(formula = streams ~ in_spotify_playlists + in_deezer_playlists + 
    in_apple_playlists + danceability_. + energy_. + valence_., 
    data = spotify)

Residuals:
       Min         1Q     Median         3Q        Max 
-1.068e+09 -1.246e+08 -3.334e+07  9.771e+07  1.301e+09 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)          182491364   45594165   4.003 6.80e-05 ***
in_spotify_playlists     42130       3557  11.844  < 2e-16 ***
in_deezer_playlists     566032      74514   7.596 7.90e-14 ***
in_apple_playlists     1507560     189228   7.967 5.11e-15 ***
danceability_.          223598     592024   0.378   0.7058    
energy_.               -717839     509189  -1.410   0.1590    
valence_.              -637129     379394  -1.679   0.0934 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 229500000 on 866 degrees of freedom
  (80 observations deleted due to missingness)
Multiple R-squared:  0.6886,    Adjusted R-squared:  0.6864 
F-statistic: 319.2 on 6 and 866 DF,  p-value: < 2.2e-16
# Refitting the model with only significant predictors
refined_model <- lm(streams ~ in_spotify_playlists + in_deezer_playlists + in_apple_playlists, data = spotify)

# Summary of the refined model
summary(refined_model)

Call:
lm(formula = streams ~ in_spotify_playlists + in_deezer_playlists + 
    in_apple_playlists, data = spotify)

Residuals:
       Min         1Q     Median         3Q        Max 
-1.082e+09 -1.173e+08 -3.519e+07  9.715e+07  1.311e+09 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)          119309688   10537578  11.322  < 2e-16 ***
in_spotify_playlists     43264       3526  12.271  < 2e-16 ***
in_deezer_playlists     565426      74598   7.580 8.88e-14 ***
in_apple_playlists     1427508     186160   7.668 4.67e-14 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.3e+08 on 869 degrees of freedom
  (80 observations deleted due to missingness)
Multiple R-squared:  0.686, Adjusted R-squared:  0.6849 
F-statistic: 632.9 on 3 and 869 DF,  p-value: < 2.2e-16
# Refit the model with complete cases only
data_complete <- spotify %>%
  select(streams, in_spotify_playlists, in_deezer_playlists, in_apple_playlists) %>%
  na.omit()

model <- lm(streams ~ in_spotify_playlists + in_deezer_playlists + in_apple_playlists, data = data_complete)

# Add predictions to the complete data

# Predict streams
predicted_streams <- predict(model, newdata = data_complete)

# Add predictions to the data frame
data_complete$predicted_streams <- predicted_streams
data_complete
# Plot actual vs predicted
ggplot(data_complete, aes(x = streams, y = predicted_streams)) +
  geom_point(alpha = 0.6, color = "steelblue") +
  geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
  labs(title = "Actual vs Predicted Streams",
       x = "Actual Streams",
       y = "Predicted Streams") +
  theme_minimal()

# Plot residuals
residuals <- model$residuals
View(data_complete)

ggplot(data_complete, aes(x = predicted_streams, y = residuals)) +
  geom_point(alpha = 0.6, color = "darkorange") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  labs(title = "Residual Plot",
       x = "Predicted Streams",
       y = "Residuals") +
  theme_minimal()

# Step 1: Load required libraries
library(caret)
G2;H2;Warningh: package ‘caret’ was built under R version 4.3.3g
G3;Loading required package: lattice
g
# Step 2: Set seed for reproducibility
set.seed(123)

# Step 3: Define training control for 10-fold cross-validation
train_control <- trainControl(method = "cv", number = 10)

# Step 4: Define the model formula (same predictors as before)
model_formula <- streams ~ in_spotify_playlists + in_deezer_playlists + in_apple_playlists

# Step 5: Fit the linear regression model using caret::train()
cv_model <- train(
  model_formula,
  data = data_complete,
  method = "lm",
  trControl = train_control
)

# Step 6: Review cross-validation results
print(cv_model)
Linear Regression 

873 samples
  3 predictor

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 785, 786, 785, 786, 786, 787, ... 
Resampling results:

  RMSE       Rsquared   MAE      
  231655068  0.6849981  161789134

Tuning parameter 'intercept' was held constant at a value of TRUE
# Optional: Plot predictions vs. actuals again using cv_model$finalModel if desired
# Fit the final model on full data
final_model <- train(
  streams ~ in_spotify_playlists + in_deezer_playlists + in_apple_playlists,
  data = data_complete,
  method = "lm"
)

# View final coefficients
coef(final_model$finalModel)
         (Intercept) in_spotify_playlists  in_deezer_playlists   in_apple_playlists 
        119309687.75             43263.74            565425.67           1427508.47 
#Create a new data frame with predictor values
# Replace these numbers with your actual input values
new_input <- data.frame(
  in_spotify_playlists = 2500,
  in_deezer_playlists = 50,
  in_apple_playlists = 250
)

# 3. Predict streams based on new inputs
predicted_streams <- predict(cv_model, newdata = new_input)

# View prediction
predicted_streams
        1 
612617449 
# Load required packages
library(shiny)

# Define UI
ui <- fluidPage(
  titlePanel("Predict Song Streams"),
  sidebarLayout(
    sidebarPanel(
      numericInput("spotify", "Spotify Playlists:", value = 5000, min = 0),
      numericInput("deezer", "Deezer Playlists:", value = 1000, min = 0),
      numericInput("apple", "Apple Playlists:", value = 2000, min = 0),
      actionButton("predict", "Predict Streams")
    ),
    mainPanel(
      h3("Predicted Streams:"),
      verbatimTextOutput("prediction")
    )
  )
)

# Define server logic
server <- function(input, output) {

  # Reactive prediction
  observeEvent(input$predict, {
    new_input <- data.frame(
      in_spotify_playlists = input$spotify,
      in_deezer_playlists = input$deezer,
      in_apple_playlists = input$apple
    )

    predicted <- predict(cv_model, newdata = new_input)

    output$prediction <- renderText({
      format(round(predicted, 0), big.mark = ",")
    })
  })
}

# Run the application 
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:6557
grsession-arm64(40396) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40397) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40398) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40399) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40400) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40401) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
?cv_model
No documentation for ‘cv_model’ in specified packages and libraries:
you could try ‘??cv_model’
cv_model
Linear Regression 

873 samples
  3 predictor

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 785, 786, 785, 786, 786, 787, ... 
Resampling results:

  RMSE       Rsquared   MAE      
  231655068  0.6849981  161789134

Tuning parameter 'intercept' was held constant at a value of TRUE
# Assume `final_model` is already trained with lm()
colSums(is.na(data_complete))
             streams in_spotify_playlists  in_deezer_playlists   in_apple_playlists    predicted_streams 
                   0                    0                    0                    0                    0 
# Then you need to extract the final linear model from the `train` object
# before using it for prediction with confidence and prediction intervals

lm_model <- cv_model$finalModel

# Now you can safely use predict with interval = "confidence" and "prediction"
pred_conf <- predict(lm_model, newdata = data_complete, interval = "confidence")
pred_pred <- predict(lm_model, newdata = data_complete, interval = "prediction")

# Combine everything into a data frame
plot_data <- data_complete %>%
  mutate(
    predicted_streams = pred_conf[, "fit"],
    conf_low = pred_conf[, "lwr"],
    conf_high = pred_conf[, "upr"],
    pred_low = pred_pred[, "lwr"],
    pred_high = pred_pred[, "upr"]
  )

# Plot
ggplot(plot_data, aes(x = streams, y = predicted_streams)) +
  geom_point(alpha = 0.6, color = "darkblue") +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "blue") +
  geom_ribbon(aes(ymin = conf_low, ymax = conf_high), fill = "lightblue", alpha = 0.3) +
  geom_ribbon(aes(ymin = pred_low, ymax = pred_high), fill = "orange", alpha = 0.2) +
  labs(
    title = "Predicted vs Actual Streams with Confidence and Prediction Intervals",
    x = "Actual Streams",
    y = "Predicted Streams"
  ) +
  theme_minimal()

# First, ensure that you have your predictions with intervals set up properly

# Extract the linear model from caret's train object
lm_model <- cv_model$finalModel

# Generate predictions with both confidence and prediction intervals
pred_conf <- predict(lm_model, newdata = data_complete, interval = "confidence")
pred_pred <- predict(lm_model, newdata = data_complete, interval = "prediction")

# Combine everything into a data frame
plot_data <- data_complete %>%
  mutate(
    predicted_streams = pred_conf[, "fit"],
    conf_low = pred_conf[, "lwr"],
    conf_high = pred_conf[, "upr"],
    pred_low = pred_pred[, "lwr"],
    pred_high = pred_pred[, "upr"]
  ) %>%
  arrange(streams)  # sort by actual streams for smooth ribbons
# Plot with ggplot2
ggplot(plot_data, aes(x = predicted_streams, y = streams)) +
  geom_point(alpha = 0.6, color = "darkblue") +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "blue") +
  geom_ribbon(aes(ymin = conf_low, ymax = conf_high), fill = "lightblue", alpha = 0.3) +
  geom_ribbon(aes(ymin = pred_low, ymax = pred_high), fill = "orange", alpha = 0.2) +
  labs(
    title = "Predicted vs Actual Streams with Confidence and Prediction Intervals",
    x = "Predicted Streams",
    y = "Actual Streams"
  ) +
  theme_minimal()

# Plot with confidence and prediction intervals as lines (no shaded ribbons)
ggplot(plot_data, aes(x = predicted_streams, y = streams)) +
  geom_point(alpha = 0.6, color = "darkblue") +
  geom_abline(intercept = 0, slope = 1, linetype = "solid", color = "blue") +
  geom_line(aes(y = conf_low), color = "purple", linetype = "dashed") +
  geom_line(aes(y = conf_high), color = "purple", linetype = "dashed") +
  geom_line(aes(y = pred_low), color = "red", linetype = "dotted") +
  geom_line(aes(y = pred_high), color = "red", linetype = "dotted") +
  labs(
    title = "Actual vs Predicted Streams with Confidence and Prediction Interval Lines (R^2 = 0.68)",
    x = "Predicted Streams",
    y = "Actual Streams"
  ) +
  theme_minimal()

ggplot(plot_data, aes(x = in_spotify_playlists, y = streams)) +
  geom_point() +
  geom_line(aes(y = predicted_streams), color = "blue") +
  geom_ribbon(aes(ymin = conf_low, ymax = conf_high), alpha = 0.2) +
  labs(title = "Model Fit with Confidence Interval",
       y = "Streams", x = "Spotify Playlists") +
  theme_minimal()

# Load required packages
library(shiny)

# Combine Visuals/Define UI
ui <- fluidPage(
  titlePanel("Predict Spotify Song Streams"),
  tabsetPanel(
    tabPanel("Visualize by Mode",
      sidebarLayout(
        sidebarPanel(
          checkboxGroupInput("selected_modes", "Select Mode(s):",
                             choices = unique(spotify$mode),
                             selected = unique(spotify$mode))
        ),
        mainPanel(
          plotOutput("modePlot")
        )
      )
    ),
    tabPanel("Predict Streams",
      sidebarLayout(
        sidebarPanel(
          numericInput("spotify", "Spotify Playlists:", value = 5000, min = 0),
          numericInput("deezer", "Deezer Playlists:", value = 1000, min = 0),
          numericInput("apple", "Apple Playlists:", value = 2000, min = 0),
          actionButton("predict", "Predict Streams")
        ),
        mainPanel(
          h3("Predicted Streams:"),
          verbatimTextOutput("prediction")
        )
      )
    )
  )
)

# Define server logic
server <- function(input, output) {

  # Reactive prediction
  observeEvent(input$predict, {
    new_input <- data.frame(
      in_spotify_playlists = input$spotify,
      in_deezer_playlists = input$deezer,
      in_apple_playlists = input$apple
    )

    predicted <- predict(final_model, newdata = new_input)

    output$prediction <- renderText({
      format(round(predicted, 0), big.mark = ",")
    })
  })

  output$modePlot <- renderPlot({
    req(input$selected_modes)

    filtered_data <- subset(spotify, mode %in% input$selected_modes)

    ggplot(filtered_data, aes(x = streams, y = in_spotify_playlists, color = mode)) +
      geom_point() +
      labs(title = "Streams vs Playlist Metrics by Mode",
           x = "Streams",
           y = "Number in Spotify Playlists") +
      theme_minimal()
  })
}

# Run the application 
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:6557
g
#single variable 
ggplot(plot_data, aes(x = predicted_streams, y = in_spotify_playlists)) +
  geom_point(aes(y = streams), alpha = 0.5) +
  geom_line() +
  geom_ribbon(aes(ymin = conf_low, ymax = conf_high), alpha = 0.2) +
  labs(title = "Prediction with Confidence Intervals")

plot(lm_model)

plot(lm_model, which = 5) 

# Calculate Cook's Distance
cooksD <- cooks.distance(lm_model)

# Set a common threshold (4 / n)
threshold <- 4 / nrow(data_complete)

# Find influential points
influential_points <- which(cooksD > threshold)
 
influential_points
 X15  X16  X23  X42  X43  X48  X52  X58  X62  X72  X75  X85  X88 X111 X116 X122 X133 X147 X153 X155 X159 X160 X165 X167 X168 X170 X171 X181 X185 X187 
  15   16   23   42   43   48   51   55   59   68   70   78   80   98  102  108  116  127  132  134  137  138  142  144  145  146  147  152  155  157 
X188 X193 X232 X240 X304 X321 X366 X367 X369 X379 X393 X396 X411 X423 X425 X426 X434 X444 X455 X458 X461 X463 X467 X470 X472 X496 X506 X508 X511 X514 
 158  161  199  207  267  284  325  326  328  338  352  355  367  379  381  382  390  397  408  411  414  416  420  423  425  449  459  461  464  467 
X520 X531 X536 X556 X558 X563 X566 X567 X576 X582 X585 X592 X599 X600 X614 X617 X620 X622 X639 X658 X675 X719 X720 X740 X765 X841 X857 X864 X900 X903 
 473  484  489  509  511  516  519  520  528  534  537  544  550  551  564  566  569  570  583  598  610  650  651  667  688  764  780  787  821  824 
X912 
 832 
# Create a new dataset excluding influential rows
data_no_influential <- data_complete[-influential_points, ]
data_no_influential
# Refit the model using caret with cross-validation
cv_model_clean <- train(
  model_formula,
  data = data_no_influential,
  method = "lm",
  trControl = trainControl(method = "cv", number = 10)
)
cv_model$results  # Original model
cv_model_clean$results  # Model without influential points
model_full <- lm(model_formula, data = data_complete)
model_reduced <- lm(model_formula, data = data_no_influential)

AIC(model_full, model_reduced)
G2;H2;Warningh in AIC.default(model_full, model_reduced) :
  models are not all fitted to the same number of observationsg
BIC(model_full, model_reduced)
G2;H2;Warningh in BIC.default(model_full, model_reduced) :
  models are not all fitted to the same number of observationsg
# First, ensure that you have your predictions with intervals set up properly

# Extract the linear model from caret's train object
lm_model_clean <- cv_model_clean$finalModel

# Generate predictions with both confidence and prediction intervals
pred_conf_clean <- predict(lm_model_clean, newdata = data_no_influential, interval = "confidence")
pred_pred_clean <- predict(lm_model_clean, newdata = data_no_influential, interval = "prediction")

# Combine everything into a data frame
plot_data_clean <- data_no_influential %>%
  mutate(
    predicted_streams = pred_conf_clean[, "fit"],
    conf_low = pred_conf_clean[, "lwr"],
    conf_high = pred_conf_clean[, "upr"],
    pred_low = pred_pred_clean[, "lwr"],
    pred_high = pred_pred_clean[, "upr"]
  ) %>%
  arrange(streams)  # sort by actual streams for smooth ribbons
# Plot with confidence and prediction intervals as lines (no shaded ribbons)
ggplot(plot_data_clean, aes(x = predicted_streams, y = streams)) +
  geom_point(alpha = 0.6, color = "darkblue") +
  geom_abline(intercept = 0, slope = 1, linetype = "solid", color = "blue") +
  geom_line(aes(y = conf_low), color = "purple", linetype = "dashed") +
  geom_line(aes(y = conf_high), color = "purple", linetype = "dashed") +
  geom_line(aes(y = pred_low), color = "red", linetype = "dotted") +
  geom_line(aes(y = pred_high), color = "red", linetype = "dotted") +
  labs(
    title = "Actual vs Predicted Streams with Confidence and Prediction Interval Lines",
    x = "Predicted Streams",
    y = "Actual Streams"
  ) +
  theme_minimal()

library(shiny)
library(dplyr)
library(ggplot2)
library(scales)
library(fmsb)

# Define UI
ui <- navbarPage(
  title = div(
    #img(src = "www/spotify_logo_black.png", height = "30px", style = "margin-top:-5px;"),
    "Spotify Data Dashboard"
  ),
  id = "main_navbar",
                 
  # Custom styling
  header = tags$head(
    tags$style(HTML("
      /* Navbar background */
      .navbar-default {
        background-color: #191414;
        border-color: #191414;
      }

      /* Navbar title and tab text */
      .navbar-default .navbar-brand,
      .navbar-default .navbar-nav > li > a {
        color: white !important;
        font-weight: bold;
      }

      /* Active tab highlight */
      .navbar-default .navbar-nav > .active > a,
      .navbar-default .navbar-nav > .active > a:focus,
      .navbar-default .navbar-nav > .active > a:hover {
        background-color: #1DB954 !important;
        color: black !important;
      }

      /* Tab content background */
      .tab-pane {
        background-color: #121212;
        color: white;
        padding: 30px;
      }

      /* Body background */
      body {
        background-color: #121212;
        color: white;
      }

      /* List styling for better visibility */
      ul {
        color: white;
      }
    "))
  ),

# --- HOME TAB ---
  tabPanel("Home",
    fluidPage(
      tags$div(
        style = "text-align: center;",
        #img(src = "www/spotify_logo_green.png", height = "120px"),
        h2("Welcome to the Spotify Data Dashboard"),
        p("This data includes songs released from 1930 - 2023. Use the tabs above to explore data visualizations and models related to Spotify songs."),
        tags$ul(
          tags$li("Explore trends across release years"),
          tags$li("Discover top streamed tracks"),
          tags$li("Visualize audio features with radar and density plots"),
          tags$li("Compare playlists and stream predictions")
        ),
        p("Use the tabs above to explore the features.")
      )
    )
  ),  
  
  # Group: Release Year Performance
  tabPanel("Release Year Performance",
    tabsetPanel(
      tabPanel("Density: Release Year",
               plotOutput("densityPlot")
      ),
      tabPanel("Top 10 Streamed Songs by Year",
               plotOutput("top10Bar")
      ),
      tabPanel("Top 5 Songs by Year",
        sidebarLayout(
          sidebarPanel(
            selectInput("year", "Select Year:", choices = sort(unique(yearly_top_songs$released_year), decreasing = TRUE))
          ),
          mainPanel(
            plotOutput("topSongsPlot")
          )
        )
      )
    )
  ),

   # Group: Audio Features
  tabPanel("Radar: Top 3 Songs Audio Features",
    sidebarLayout(
      sidebarPanel(
        selectInput("selected_year", "Choose a Year:",
                    choices = sort(unique(spotify$released_year), decreasing = TRUE),
                    selected = max(spotify$released_year, na.rm = TRUE))
      ),
      mainPanel(
        plotOutput("radarPlot")
      )
    )
  ),
  
  # Group: Mode & Playlist Insights
  tabPanel("Scatter: Streams vs Playlists by Mode",
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("mode_select", "Select Mode(s):",
                           choices = unique(spotify$mode),
                           selected = unique(spotify$mode))
      ),
      mainPanel(
        plotOutput("scatterPlot")
      )
    )
  ),


  # Group: Playlist Metrics & Predictions
  tabPanel("Playlists & Streams Relationship",
    tabsetPanel(
      tabPanel("Pairs Plot",
               plotOutput("pairsPlot")
      ),
      tabPanel("Model Predictions",
        fluidRow(
          column(6,
            h4("Predict Streams"),
            numericInput("spotify", "Spotify Playlists:", value = 5000, min = 0),
            numericInput("deezer", "Deezer Playlists:", value = 1000, min = 0),
            numericInput("apple", "Apple Playlists:", value = 2000, min = 0),
            actionButton("predict", "Predict Streams"),
            br(), br(),
            h5("Predicted Streams:"),
            verbatimTextOutput("prediction")
          ),
          column(6,
            h4("Actual vs Predicted Streams"),
            plotOutput("predictionPlot")
          )
        )
      )
    )
  )
)



# Define Server
server <- function(input, output, session) {
  
  # Density Plot ----
  output$densityPlot <- renderPlot({
    plot(density(spotify$released_year, na.rm = TRUE),
         main = "Density Plot of Released Year",
         xlab = "Released Year", col = "blue", lwd = 2)
  })
  
  # Scatter Plot ----
  output$scatterPlot <- renderPlot({
    filtered_data <- spotify[spotify$mode %in% input$mode_select, ]
    
    ggplot(filtered_data, aes(x = streams, y = in_spotify_playlists, color = mode)) +
      geom_point() +
      labs(title = "Streams vs Playlist Metrics by Mode",
           x = "Streams", y = "Number in Spotify Playlists") +
      theme_minimal()
  })
  
  # Top 10 Streamed Songs by Year ----
  output$top10Bar <- renderPlot({
    ggplot(top10_yearly, aes(x = factor(released_year), y = streams, fill = song.artist)) +
      geom_bar(stat = "identity") +
      labs(title = "Top 10 Streamed Songs By Year",
           x = "Year", y = "Streams", fill = "Song - Artist") +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # Top 5 Songs per Year ----
  output$topSongsPlot <- renderPlot({
    selected_year_data <- yearly_top_songs %>%
      filter(released_year == input$year)
    
    selected_year_data$track_name <- iconv(selected_year_data$track_name, from = "UTF-8", to = "UTF-8", sub = "*")
    
    ggplot(selected_year_data, aes(x = reorder(track_name, -streams), y = streams, fill = song.artist)) +
      geom_bar(stat = "identity") +
      labs(title = paste("Top 5 Streamed Songs in", input$year),
           x = "Song", y = "Streams", fill = "Song - Artist") +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # Radar Chart ----
  radar_matrix3 <- reactive({
    req(input$selected_year)
    top3_year <- spotify %>%
      filter(released_year == input$selected_year) %>%
      slice_max(order_by = streams, n = 3, with_ties = FALSE) %>%
      dplyr::rename(
        danceability = `danceability_.`,
        speechiness = `speechiness_.`,
        energy = `energy_.`,
        acousticness = `acousticness_.`
      ) %>%
      dplyr::select(song.artist, bpm, danceability, speechiness, energy, acousticness)
    
    radar_data_norm3 <- top3_year %>%
      mutate(across(where(is.numeric), ~ rescale(.x, to = c(0, 1))))
    
    if (nrow(radar_data_norm3) < 1) return(NULL)
    
    max_min3 <- data.frame(
      bpm = 1, danceability = 1, speechiness = 1, energy = 1, acousticness = 1,
      row.names = c("Max")
    ) %>%
      bind_rows(data.frame(
        bpm = 0, danceability = 0, speechiness = 0, energy = 0, acousticness = 0,
        row.names = c("Min")
      ))
    
    bind_rows(max_min3, radar_data_norm3 %>% column_to_rownames("song.artist"))
  })
  
  output$radarPlot <- renderPlot({
    matrix <- radar_matrix3()
    req(matrix)
    
    colors_border <- rainbow(nrow(matrix) - 2)
    colors_in <- adjustcolor(colors_border, alpha.f = 0.25)
    
    layout(matrix(c(1, 2), nrow = 2), heights = c(4, 1))
    par(mar = c(2, 2, 4, 2))
    radarchart(
      matrix,
      axistype = 1,
      pcol = colors_border,
      pfcol = colors_in,
      plwd = 2,
      plty = 1,
      cglcol = "grey",
      cglty = 1,
      axislabcol = "grey",
      caxislabels = seq(0, 1, 0.2),
      cglwd = 0.8,
      vlcex = 0.9,
      title = paste("Top 3 Songs in", input$selected_year)
    )
    
    par(mar = c(0, 0, 0, 0))
    plot.new()
    legend("center", legend = rownames(matrix)[-c(1, 2)],
           bty = "n", pch = 20, col = colors_border, text.col = "black", cex = 0.9)
  })
  
  # Pairs Plot ----
  output$pairsPlot <- renderPlot({
    selected_data <- dplyr::select(spotify, in_spotify_playlists, in_deezer_playlists, in_apple_playlists, streams)
    pairs(selected_data, main = "Pairs Plot of Playlist Counts and Streams")
  })
  
  # Actual vs Predicted Plot ----
  output$predictionPlot <- renderPlot({
    ggplot(plot_data, aes(x = predicted_streams, y = streams)) +
      geom_point(alpha = 0.6, color = "darkblue") +
      geom_abline(intercept = 0, slope = 1, linetype = "solid", color = "blue") +
      geom_line(aes(y = conf_low), color = "purple", linetype = "dashed") +
      geom_line(aes(y = conf_high), color = "purple", linetype = "dashed") +
      geom_line(aes(y = pred_low), color = "red", linetype = "dotted") +
      geom_line(aes(y = pred_high), color = "red", linetype = "dotted") +
      labs(title = "Actual vs Predicted Streams (R^2 = 0.68)",
           x = "Predicted Streams", y = "Actual Streams") +
      theme_minimal()
  })
  
  # Predict Streams ----
  observeEvent(input$predict, {
    new_input <- data.frame(
      in_spotify_playlists = input$spotify,
      in_deezer_playlists = input$deezer,
      in_apple_playlists = input$apple
    )
    
    predicted <- predict(cv_model, newdata = new_input)
    
    output$prediction <- renderText({
      format(round(predicted, 0), big.mark = ",")
    })
  })
}

# Run the App
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:6557
grsession-arm64(40420) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40421) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40422) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40423) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40424) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
rsession-arm64(40425) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
ui <- fluidPage(
  img(src = "spotify_logo_black.png", height = "100px")
)

server <- function(input, output) {}

shinyApp(ui, server)
G3;
Listening on http://127.0.0.1:6557
g
file.exists("www:spotify_logo_black.png")
[1] FALSE
list.files("www")
character(0)
getwd()
[1] "/Users/ryanoka/Documents/DS Bootcamp/R/R-Project"

G1;H1;Errorh: unexpected ‘<’ in “<” Error during wrapup: not that many frames on the stack Error: no more error handlers available (recursive errors?); invoking ‘abort’ restart g

list.files("www/")
[1] "spotify_logo_black.png" "spotify_logo_green.png"
title: "Spotify Dashboard"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    runtime: shiny

img(src = "spotify_logo_black.png", height = "30px")
YAML
G1;H1;Errorh: object 'YAML' not found
gG1;H1;Error during wrapuph: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
---
title: "Spotify Data Dashboard"
G1;H1;Errorh in -title : invalid argument to unary operator
gG1;H1;Error during wrapuph: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
output: html_document
G1;H1;Errorh: object 'output' not found
Error during wrapup: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
runtime: shiny
G1;H1;Errorh: object 'runtime' not found
gG1;H1;Error during wrapuph: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
---
shiny::addResourcePath("www", "www")
G1;H1;Errorh in -shiny::addResourcePath("www", "www") : 
  invalid argument to unary operator
Error during wrapup: not that many frames on the stack
Error: no more error handlers available (recursive errors?); invoking 'abort' restart
g
# Define UI
ui <- navbarPage(
  title = div(
    img(src = "www/spotify_logo_green.png", height = "30px", style = "margin-top:-5px;"),
    "Spotify Data Dashboard"
  ),
  id = "main_navbar",
                 
  # Custom styling
  header = tags$head(
  tags$style(HTML("
    /* Navbar background */
    .navbar-default {
      background-color: #191414;
      border-color: #191414;
    }

    /* Navbar title (brand) text color */
    .navbar-default .navbar-brand {
      color: #1DB954 !important; /* Spotify green */
    }

    /* Navbar title hover/focus */
    .navbar-default .navbar-brand:hover,
    .navbar-default .navbar-brand:focus {
      color: #1ed760 !important;
    }

    /* Tab panel (menu items) text color */
    .navbar-default .navbar-nav > li > a {
      color: #1DB954 !important;
    }

    /* Tab hover/focus */
    .navbar-default .navbar-nav > li > a:hover,
    .navbar-default .navbar-nav > li > a:focus {
      color: #1ed760 !important;
      background-color: transparent;
    }

    /* Keep main panel background */
    .main-panel, .col-sm-8 {
      background-color: #f5f5f5 !important;
      color: black !important;
      padding: 20px;
      border-radius: 6px;
    }
  "))
),

# --- HOME TAB ---
  tabPanel("Home",
    fluidPage(
      tags$div(
        style = "text-align: left;",
        img(src = "www/spotify_logo_black.png", height = "120px"),
        h2("Welcome to the Spotify Data Dashboard"),
        p("This data includes songs released from 1930 - 2023. Use the tabs above to explore data visualizations and models related to Spotify songs."),
        tags$ul(
          tags$li("Explore trends across release years"),
          tags$li("Discover top streamed tracks"),
          tags$li("Visualize audio features with radar and scatter plots"),
          tags$li("Compare playlists and stream predictions")
        ),
        p("Use the tabs above to explore the features.")
      )
    )
  ),  
  

  # Group: Release Year Performance
  tabPanel("Release Year Performance",
    tabsetPanel(
      tabPanel("Density: Release Year",
               plotOutput("densityPlot")
      ),
      tabPanel("Top 10 Overall - Top Streamed Song From Each Released Year",
               plotOutput("top10Bar"),
               p('Filtering for the top streamed from each released year and then displaying which songs ranked in top 10 by overall stream counts.')
      ),
      tabPanel("Top 5 Songs by Year",
        sidebarLayout(
          sidebarPanel(
            selectInput("year", "Select Year:", choices = sort(unique(yearly_top_songs$released_year), decreasing = TRUE))
          ),
          mainPanel(
            plotOutput("topSongsPlot")
          )
        )
      )
    )
  ),

   # Group: Audio Features
  tabPanel("Radar: Top 3 Songs Audio Features",
    sidebarLayout(
      sidebarPanel(
        selectInput("selected_year", "Choose a Year:",
                    choices = sort(unique(spotify$released_year), decreasing = TRUE),
                    selected = max(spotify$released_year, na.rm = TRUE)),
        tags$ul(
          style = "padding-left: 15px; margin-left: 0;", # Adjust padding as needed
          tags$li("BPM - Beats per minute, tempo or speed of the song. "),
          tags$li("Acousticness - Confidence measure of how much a track sounds like it was made with live instruments and natural sounds, rather than electronic or synthesized sounds. "),
          tags$li("Energy - Encompasses a broader range of elements, including momentum, intensity, and emotional impact."),
          tags$li("Speechiness - Measure of how much spoken word content is present in a track, as opposed to purely musical elements."),
          tags$li("Danceability - Measure of how easily someone could move their body to the rhythm and structure of the music."),
        )
      ),
      mainPanel(
        plotOutput("radarPlot")
      )
    )
  ),


  # Group: Playlist Metrics & Predictions
  tabPanel("Playlists & Streams Relationship",
    tabsetPanel(
    tabPanel("Scatter: Streams vs Playlists by Mode",
    sidebarLayout(
      sidebarPanel(
        checkboxGroupInput("mode_select", "Select Mode(s):",
                           choices = unique(spotify$mode),
                           selected = unique(spotify$mode)),
        tags$ul(
          style = "padding-left: 15px; margin-left: 0;", # Adjust padding as needed
          tags$li("Modes refer to two different types of musical scales and keys."),
          tags$li("Major - Sounds bright, happy, and uplifting."),
          tags$li("Minor - Sounds sad, melancholic, or dark."),
        )
      ),
       
      mainPanel(
        plotOutput("scatterPlot"),
      )
    )
  ),
      
      
      tabPanel("Pairs Plot",
               plotOutput("pairsPlot"),
               p('The pairs plot is showing the linear relationship between the number of streams to the number of playlists songs appear in. Spotify, Deezer, and Apple plalylist appearances had the most linear relationship to streams.')
      ),
      tabPanel("Model Predictions",
        fluidRow(
          column(6,
            p('Input an estimate for the number of appearances in each of the playlists in order to predict the number of streams. The model is trained on this data to predict the number of total streams (2008-2024). 
             '),
            h4("Predict Streams"),
            numericInput("spotify", "Spotify Playlists:", value = 5000, min = 0),
            numericInput("deezer", "Deezer Playlists:", value = 1000, min = 0),
            numericInput("apple", "Apple Playlists:", value = 2000, min = 0),
            actionButton("predict", "Predict Streams"),
            br(), br(),
            h5("Predicted Streams:"),
            verbatimTextOutput("prediction")
          ),
          
          column(6,
            h4("Actual vs Predicted Streams"),
            plotOutput("predictionPlot"),
             p('The linear model displayed is trained on streams in relation to Spotify, Deezer, and Apple playlist appearances. Using data from each song, it is plotting the actual stream numbers to the predicted value with confidence and predictive intervals.')
          )
        )
      )
    )
  )
)



# Define Server
server <- function(input, output, session) {
  
  # Density Plot ----
  output$densityPlot <- renderPlot({
    plot(density(spotify$released_year, na.rm = TRUE),
         main = "Density Plot of Song Counts by Released Year",
         xlab = "Released Year", col = "blue", lwd = 2)
  })
  
  # Scatter Plot ----
  output$scatterPlot <- renderPlot({
    filtered_data <- spotify[spotify$mode %in% input$mode_select, ]
    
    ggplot(filtered_data, aes(x = streams, y = in_spotify_playlists, color = mode)) +
      geom_point() +
      labs(title = "Streams vs Playlist Metrics by Mode",
           x = "Streams", y = "Number in Spotify Playlists") +
      theme_minimal()
  })
  
  # Top 10 Streamed Songs by Year ----
  output$top10Bar <- renderPlot({
    ggplot(top10_yearly, aes(x = factor(released_year), y = streams, fill = song.artist)) +
      geom_bar(stat = "identity") +
      labs(title = "Top Streamed Song For Each Released Year: Top 10 Ranking Overall",
           x = "Year", y = "Streams", fill = "Song - Artist") +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # Top 5 Songs per Year ----
  output$topSongsPlot <- renderPlot({
    selected_year_data <- yearly_top_songs %>%
      filter(released_year == input$year)
    
    selected_year_data$track_name <- iconv(selected_year_data$track_name, from = "UTF-8", to = "UTF-8", sub = "*")
    
    ggplot(selected_year_data, aes(x = reorder(track_name, -streams), y = streams, fill = song.artist)) +
      geom_bar(stat = "identity") +
      labs(title = paste("Top 5 Streamed Songs in", input$year),
           x = "Song", y = "Streams", fill = "Song - Artist") +
      theme_minimal() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  })
  
  # Radar Chart ----
  radar_matrix3 <- reactive({
    req(input$selected_year)
    top3_year <- spotify %>%
      filter(released_year == input$selected_year) %>%
      slice_max(order_by = streams, n = 3, with_ties = FALSE) %>%
      dplyr::rename(
        danceability = `danceability_.`,
        speechiness = `speechiness_.`,
        energy = `energy_.`,
        acousticness = `acousticness_.`
      ) %>%
      dplyr::select(song.artist, bpm, danceability, speechiness, energy, acousticness)
    
    radar_data_norm3 <- top3_year %>%
      mutate(across(where(is.numeric), ~ rescale(.x, to = c(0, 1))))
    
    if (nrow(radar_data_norm3) < 1) return(NULL)
    
    max_min3 <- data.frame(
      bpm = 1, danceability = 1, speechiness = 1, energy = 1, acousticness = 1,
      row.names = c("Max")
    ) %>%
      bind_rows(data.frame(
        bpm = 0, danceability = 0, speechiness = 0, energy = 0, acousticness = 0,
        row.names = c("Min")
      ))
    
    bind_rows(max_min3, radar_data_norm3 %>% column_to_rownames("song.artist"))
  })
  
  output$radarPlot <- renderPlot({
    matrix <- radar_matrix3()
    req(matrix)
    
    colors_border <- rainbow(nrow(matrix) - 2)
    colors_in <- adjustcolor(colors_border, alpha.f = 0.25)
    
    layout(matrix(c(1, 2), nrow = 2), heights = c(4, 1))
    par(mar = c(2, 2, 4, 2))
    radarchart(
      matrix,
      axistype = 1,
      pcol = colors_border,
      pfcol = colors_in,
      plwd = 2,
      plty = 1,
      cglcol = "grey",
      cglty = 1,
      axislabcol = "grey",
      caxislabels = seq(0, 1, 0.2),
      cglwd = 0.8,
      vlcex = 0.9,
      title = paste("Top 3 Songs in", input$selected_year)
    )
    
    par(mar = c(0, 0, 0, 0))
    plot.new()
    legend("center", legend = rownames(matrix)[-c(1, 2)],
           bty = "n", pch = 20, col = colors_border, text.col = "black", cex = 0.9)
  })
  
  # Pairs Plot ----
  output$pairsPlot <- renderPlot({
    selected_data <- dplyr::select(spotify, in_spotify_playlists, in_deezer_playlists, in_apple_playlists, streams)
    pairs(selected_data, main = "Pairs Plot of Playlist Counts and Streams")
  })
  
  # Actual vs Predicted Plot ----
  output$predictionPlot <- renderPlot({
    ggplot(plot_data, aes(x = predicted_streams, y = streams)) +
      geom_point(alpha = 0.6, color = "darkblue") +
      geom_abline(intercept = 0, slope = 1, linetype = "solid", color = "blue") +
      geom_line(aes(y = conf_low), color = "purple", linetype = "dashed") +
      geom_line(aes(y = conf_high), color = "purple", linetype = "dashed") +
      geom_line(aes(y = pred_low), color = "red", linetype = "dotted") +
      geom_line(aes(y = pred_high), color = "red", linetype = "dotted") +
      labs(title = "Actual vs Predicted Streams (R^2 = 0.68)",
           x = "Predicted Streams", y = "Actual Streams") +
      theme_minimal()
  })
  
  # Predict Streams ----
  observeEvent(input$predict, {
    new_input <- data.frame(
      in_spotify_playlists = input$spotify,
      in_deezer_playlists = input$deezer,
      in_apple_playlists = input$apple
    )
    
    predicted <- predict(cv_model, newdata = new_input)
    
    output$prediction <- renderText({
      format(round(predicted, 0), big.mark = ",")
    })
  })
}

# Run the App
shinyApp(ui = ui, server = server)
G3;
Listening on http://127.0.0.1:4945
gG2;H2;Warningh: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).g
rsession-arm64(34332) MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
LS0tCnRpdGxlOiAiUiBQcm9qZWN0IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLgoKVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkNtZCtTaGlmdCtFbnRlciouCgpgYGB7cn0Kc3BvdGlmeSA8LSByZWFkLmNzdigiUG9wdWxhcl9TcG90aWZ5X1NvbmdzLmNzdiIpCmhlYWQoc3BvdGlmeSkKYGBgCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ21kK09wdGlvbitJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ21kK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuCgpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuCgpgYGB7cn0Kc3BvdGlmeSA8LSByZWFkLmNzdigiUG9wdWxhcl9TcG90aWZ5X1NvbmdzLmNzdiIpCmhlYWQoc3BvdGlmeSkKYGBgCgpgYGB7cn0KI3Nwb3RpZnlbY29sc190b19jb252ZXJ0XSA8LSBsYXBwbHkoc3BvdGlmeVtjb2xzX3RvX2NvbnZlcnRdLCBmdW5jdGlvbih4KSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4KSkpCgpzcG90aWZ5JHN0cmVhbXMgPC0gYXMubnVtZXJpYyhzcG90aWZ5JHN0cmVhbXMpCnNwb3RpZnkkaW5fZGVlemVyX3BsYXlsaXN0cyA9IGFzLm51bWVyaWMoc3BvdGlmeSRpbl9kZWV6ZXJfcGxheWxpc3RzKQpzcG90aWZ5JGluX3NoYXphbV9jaGFydHMgPSBhcy5udW1lcmljKHNwb3RpZnkkaW5fc2hhemFtX2NoYXJ0cykKc3RyKHNwb3RpZnlbLCAzOjE0XSkKYGBgCgpgYGB7cn0KcGFpcnMoc3BvdGlmeVssIDM6MTRdLCBtYWluID0gIkxpbmVhciBSZWxhdGlvbnNoaXBzIEJldHdlZW4gTWV0cmljcyIpCmBgYAoKYGBge3J9CiMgU2VsZWN0IG9ubHkgdGhlIHJlbGV2YW50IGNvbHVtbnMKc2VsZWN0ZWRfZGF0YSA8LSBzcG90aWZ5WywgYygic3RyZWFtcyIsICJpbl9zcG90aWZ5X3BsYXlsaXN0cyIsICJpbl9kZWV6ZXJfcGxheWxpc3RzIiwgImluX2FwcGxlX3BsYXlsaXN0cyIpXQoKIyBDcmVhdGUgdGhlIHBhaXJzIHBsb3QKcGFpcnMoc2VsZWN0ZWRfZGF0YSwgbWFpbiA9ICJQYWlycyBQbG90IG9mIFBsYXlsaXN0IENvdW50cyBhbmQgU3RyZWFtcyIpCgpgYGAKCmBgYHtyfQoKYGBgCgpgYGB7cn0KY29sU3Vtcyhpcy5uYShzcG90aWZ5KSkKZGltKHNwb3RpZnkpCmBgYAoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKCmdncGxvdChzcG90aWZ5LCBhZXMoeCA9IHJlbGVhc2VkX3llYXIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJ3aGl0ZSIpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBTdHJlYW1zIiwgeCA9IG5hbWVzKHNwb3RpZnkkcmVsZWFzZWRfeWVhcikpCgpwbG90KGRlbnNpdHkoc3BvdGlmeSRyZWxlYXNlZF95ZWFyLCBuYS5ybSA9IFRSVUUpLCBtYWluID0gIkRlbnNpdHkgUGxvdCBvZiBSZWxlYXNlZCBZZWFyIiwgeGxhYiA9ICJSZWxlYXNlZCBZZWFyIiwgY29sID0gImJsdWUiLCBsd2QgPSAyKQpgYGAKCmBgYHtyfQpWaWV3KHNwb3RpZnkpCmBgYAoKYGBge3J9CiMgQmFzaWMgc2NhdHRlciBwbG90IHdpdGggY29sb3IgYmFzZWQgb24gJ21vZGUnCmdncGxvdChzcG90aWZ5LCBhZXMoeCA9IHN0cmVhbXMsIHkgPSBpbl9zcG90aWZ5X3BsYXlsaXN0cywgY29sb3IgPSBtb2RlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICJTdHJlYW1zIHZzIFBsYXlsaXN0IE1ldHJpY3MgYnkgTW9kZSIsCiAgICAgICB4ID0gIlN0cmVhbXMiLAogICAgICAgeSA9ICJOdW1iZXIgaW4gU3BvdGlmeSBQbGF5bGlzdHMiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2dwbG90MikKYGBgCgoKYGBge3J9CiMgVUkKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIlN0cmVhbXMgdnMgU3BvdGlmeSBQbGF5bGlzdHMgYnkgTW9kZSIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIGNoZWNrYm94R3JvdXBJbnB1dCgibW9kZV9zZWxlY3QiLCAiU2VsZWN0IE1vZGUocyk6IiwKICAgICAgICAgICAgICAgICAgICAgICAgIGNob2ljZXMgPSB1bmlxdWUoc3BvdGlmeSRtb2RlKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gdW5pcXVlKHNwb3RpZnkkbW9kZSkpCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICBwbG90T3V0cHV0KCJzY2F0dGVyUGxvdCIpCiAgICApCiAgKQopCgojIFNlcnZlcgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIG91dHB1dCRzY2F0dGVyUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGZpbHRlcmVkX2RhdGEgPC0gc3BvdGlmeVtzcG90aWZ5JG1vZGUgJWluJSBpbnB1dCRtb2RlX3NlbGVjdCwgXQoKICAgIGdncGxvdChmaWx0ZXJlZF9kYXRhLCBhZXMoeCA9IHN0cmVhbXMsIHkgPSBpbl9zcG90aWZ5X3BsYXlsaXN0cywgY29sb3IgPSBtb2RlKSkgKwogICAgICBnZW9tX3BvaW50KCkgKwogICAgICBsYWJzKAogICAgICAgIHRpdGxlID0gIlN0cmVhbXMgdnMgUGxheWxpc3QgTWV0cmljcyBieSBNb2RlIiwKICAgICAgICB4ID0gIlN0cmVhbXMiLAogICAgICAgIHkgPSAiTnVtYmVyIGluIFNwb3RpZnkgUGxheWxpc3RzIgogICAgICApICsKICAgICAgdGhlbWVfbWluaW1hbCgpCiAgfSkKfQoKIyBSdW4gdGhlIGFwcApzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCgoKYGBgCgpgYGB7cn0KbGlicmFyeShkcGx5cikKIyBDcmVhdGUgYSBjb21iaW5lZCBsYWJlbCBvZiBTb25nICsgQXJ0aXN0CnNwb3RpZnkgPC0gc3BvdGlmeSAlPiUKICBtdXRhdGUoc29uZy5hcnRpc3QgPSBwYXN0ZSh0cmFja19uYW1lLCAiLSIsIGFydGlzdC5zLl9uYW1lKSkKdmlldyhzcG90aWZ5KQpgYGAKCmBgYHtyfQp5ZWFybHlfdG9wX3NvbmcgPC0gc3BvdGlmeSAlPiUKICBncm91cF9ieShyZWxlYXNlZF95ZWFyKSAlPiUKICBzbGljZV9tYXgob3JkZXJfYnkgPSBzdHJlYW1zLCBuID0gMSwgd2l0aF90aWVzID0gVFJVRSkgJT4lCiAgdW5ncm91cCgpCgojIFN0ZXAgMTogR2V0IHRoZSB0b3AgMTAgc29uZ3MgYnkgdG90YWwgc3RyZWFtcwp0b3AxMF95ZWFybHkgPC0geWVhcmx5X3RvcF9zb25nICU+JSAKICBhcnJhbmdlKGRlc2Moc3RyZWFtcykpICU+JSAKICBzbGljZSgxOjEwKQoKdG9wMTBfeWVhcmx5CmBgYAoKYGBge3J9CgojIENvbnZlcnQgc29uZ19hcnRpc3QgdG8gZmFjdG9yIHdpdGggbGV2ZWxzIG9yZGVyZWQgYnkgU3RyZWFtcwp0b3AxMF95ZWFybHkgPC0gdG9wMTBfeWVhcmx5ICU+JQogIGFycmFuZ2UoZGVzYyhzdHJlYW1zKSkgJT4lCiAgbXV0YXRlKHNvbmcuYXJ0aXN0ID0gZmFjdG9yKHNvbmcuYXJ0aXN0LCBsZXZlbHMgPSB1bmlxdWUoc29uZy5hcnRpc3QpKSkKdG9wMTBfeWVhcmx5CmBgYAoKYGBge3J9CmdncGxvdCh0b3AxMF95ZWFybHksIGFlcyh4ID0gcmVsZWFzZWRfeWVhciwgeSA9IHN0cmVhbXMsIGZpbGwgPSBmYWN0b3Ioc29uZy5hcnRpc3QpKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgbGFicyh0aXRsZSA9ICJUb3AgU3RyZWFtZWQgU29uZ3MgcGVyIFllYXIiLAogICAgICAgeCA9ICJUcmFjayAoU29uZyAtIEFydGlzdCkiLAogICAgICAgeSA9ICJOdW1iZXIgb2YgU3RyZWFtcyIsCiAgICAgICBmaWxsID0gIlllYXIiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodG9wMTBfeWVhcmx5LCBhZXMoeCA9IGZhY3RvcihyZWxlYXNlZF95ZWFyKSwgeSA9IHN0cmVhbXMsIGZpbGwgPSBzb25nLmFydGlzdCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGxhYnModGl0bGUgPSAiVG9wIFN0cmVhbWVkIFNvbmcgRm9yIEVhY2ggUmVsZWFzZWQgWWVhcjogVG9wIDEwIFJhbmtpbmcgT3ZlcmFsbCIsCiAgICAgICB4ID0gIlllYXIiLAogICAgICAgeSA9ICJTdHJlYW1zIiwKICAgICAgIGZpbGwgPSAiU29uZyAtIEFydGlzdCIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKYGBge3J9CnllYXJseV90b3Bfc29uZ3MgPC0gc3BvdGlmeSAlPiUKICBncm91cF9ieShyZWxlYXNlZF95ZWFyKSAlPiUKICBzbGljZV9tYXgob3JkZXJfYnkgPSBzdHJlYW1zLCBuID0gNSwgd2l0aF90aWVzID0gVFJVRSkgJT4lCiAgdW5ncm91cCgpCgpWaWV3KHllYXJseV90b3Bfc29uZ3MpCmBgYAoKYGBge3J9CiMgRmlsdGVyIGZvciAyMDIzIHRvcCA1IHNvbmdzIGZyb20geW91ciBwcmV2aW91c2x5IGZpbHRlcmVkIGRhdGEKdG9wXzIwMjMgPC0geWVhcmx5X3RvcF9zb25ncyAlPiUKICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSAyMDIzKQoKIyBDcmVhdGUgdGhlIGJhciBjaGFydApnZ3Bsb3QodG9wXzIwMjMsIGFlcyh4ID0gcmVvcmRlcih0cmFja19uYW1lLCAtc3RyZWFtcyksIHkgPSBzdHJlYW1zLCBmaWxsID0gc29uZy5hcnRpc3QpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBsYWJzKHRpdGxlID0gIlRvcCA1IFN0cmVhbWVkIFNvbmdzIGluIDIwMjMiLAogICAgICAgeCA9ICJTb25nIiwKICAgICAgIHkgPSAiU3RyZWFtcyIsCiAgICAgICBmaWxsID0gIlNvbmcgLSBBcnRpc3QiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCmBgYHtyfQojIENoZWNrIGhvdyBtYW55IHJvd3MgYXJlIGluIHRoZSBkYXRhc2V0IGZvciAyMDIyCmRpbSh5ZWFybHlfdG9wX3NvbmdzW3llYXJseV90b3Bfc29uZ3MkcmVsZWFzZWRfeWVhciA9PSAyMDIyLCBdKQoKIyBDaGVjayBmb3IgTkEgb3IgaW52YWxpZCB2YWx1ZXMgaW4gMjAyMgpzdW1tYXJ5KHllYXJseV90b3Bfc29uZ3NbeWVhcmx5X3RvcF9zb25ncyRyZWxlYXNlZF95ZWFyID09IDIwMjIsIF0pCgojIEFsdGVybmF0aXZlbHksIHByaW50IGl0IHRvIGluc3BlY3QKcHJpbnQoeWVhcmx5X3RvcF9zb25nc1t5ZWFybHlfdG9wX3NvbmdzJHJlbGVhc2VkX3llYXIgPT0gMjAyMiwgXSkKYGBgCgpgYGB7cn0KIyBMb29rIGF0IDIwMjIgZGF0YSBjbG9zZWx5CnNwb3RpZnkgJT4lCiAgZmlsdGVyKHJlbGVhc2VkX3llYXIgPT0gMjAyMikgJT4lCiAgc2VsZWN0KHRyYWNrX25hbWUsIGFydGlzdC5zLl9uYW1lLCBzdHJlYW1zKSAlPiUKICBnbGltcHNlKCkKCiMgQ291bnQgdW5pcXVlIHNvbmdzIHRvIHNlZSBpZiB0aGVyZSdzIGEgdGllIGlzc3VlCnllYXJseV90b3Bfc29uZ3MgJT4lCiAgZmlsdGVyKHJlbGVhc2VkX3llYXIgPT0gMjAyMikgJT4lCiAgY291bnQodHJhY2tfbmFtZSkKCiMgQ2hlY2sgZm9yIE5BcyBpbiBzdHJlYW1zIG9yIGdyb3VwaW5nIHZhcmlhYmxlcwp5ZWFybHlfdG9wX3NvbmdzICU+JQogIGZpbHRlcihyZWxlYXNlZF95ZWFyID09IDIwMjIpICU+JQogIHN1bW1hcmlzZSgKICAgIG1pc3Npbmdfc3RyZWFtcyA9IHN1bShpcy5uYShzdHJlYW1zKSksCiAgICBtaXNzaW5nX3RyYWNrID0gc3VtKGlzLm5hKHRyYWNrX25hbWUpKSwKICAgIG1pc3NpbmdfYXJ0aXN0ID0gc3VtKGlzLm5hKHNvbmcuYXJ0aXN0KSkpCmBgYAoKYGBge3J9CiMgU2hpbnkgYXBwIHRvIHZpZXcgdG9wIHN0cmVhbWVkIHNvbmdzIGJ5IHllYXIgd2l0aCBhIHRvZ2dsZQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCgojIFVJCnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJUb3AgU3RyZWFtZWQgU29uZ3MgYnkgWWVhciIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIHNlbGVjdElucHV0KCJ5ZWFyIiwgIlNlbGVjdCBZZWFyOiIsIGNob2ljZXMgPSBzb3J0KHVuaXF1ZSh5ZWFybHlfdG9wX3NvbmdzJHJlbGVhc2VkX3llYXIpKSkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIHBsb3RPdXRwdXQoInRvcFNvbmdzUGxvdCIpCiAgICApCiAgKQopCgojIFNlcnZlcgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIAogIG91dHB1dCR0b3BTb25nc1Bsb3QgPC0gcmVuZGVyUGxvdCh7CiAgICBzZWxlY3RlZF95ZWFyX2RhdGEgPC0geWVhcmx5X3RvcF9zb25ncyAlPiUgCiAgICAgIGZpbHRlcihyZWxlYXNlZF95ZWFyID09IGlucHV0JHllYXIpCgogICAgIyBFbnN1cmUgbm8gaW52YWxpZCBjaGFyYWN0ZXJzIG9yIGVuY29kaW5nIGlzc3VlcyBpbiB0cmFjayBuYW1lcwogICAgc2VsZWN0ZWRfeWVhcl9kYXRhJHRyYWNrX25hbWUgPC0gaWNvbnYoc2VsZWN0ZWRfeWVhcl9kYXRhJHRyYWNrX25hbWUsIGZyb20gPSAiVVRGLTgiLCB0byA9ICJVVEYtOCIsIHN1YiA9ICIqIikKCiAgICBnZ3Bsb3Qoc2VsZWN0ZWRfeWVhcl9kYXRhLCBhZXMoeCA9IHJlb3JkZXIodHJhY2tfbmFtZSwgLXN0cmVhbXMpLCB5ID0gc3RyZWFtcywgZmlsbCA9IHNvbmcuYXJ0aXN0KSkgKwogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIlRvcCA1IFN0cmVhbWVkIFNvbmdzIGluIiwgaW5wdXQkeWVhciksCiAgICAgICAgICAgeCA9ICJTb25nIiwKICAgICAgICAgICB5ID0gIk51bWJlciBvZiBTdHJlYW1zIiwKICAgICAgICAgICBmaWxsID0gIlNvbmcgJiBBcnRpc3QiKSArCiAgICAgIHRoZW1lX21pbmltYWwoKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCiAgfSkKfQoKIyBSdW4gYXBwCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKCgpgYGAKCmBgYHtyfQp2aWV3KHRvcF8yMDIzKQpuYW1lcyh0b3BfMjAyMykKYGBgCgpgYGB7cn0KIyBMb2FkIHBhY2thZ2VzIGV4cGxpY2l0bHkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKCiMgTm93IGV4cGxpY2l0bHkgY2FsbCBkcGx5cjo6c2VsZWN0KCkgdG8gYXZvaWQgbWFza2luZwp0b3BfMjAyM19mZWF0dXJlcyA8LSB0b3BfMjAyMyAlPiUKICBkcGx5cjo6c2VsZWN0KAogICAgc29uZy5hcnRpc3QsCiAgICBicG0sCiAgICBgZGFuY2VhYmlsaXR5Xy5gLAogICAgYHNwZWVjaGluZXNzXy5gLAogICAgYGVuZXJneV8uYCwKICAgIGBhY291c3RpY25lc3NfLmAKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gLXNvbmcuYXJ0aXN0LCBuYW1lc190byA9ICJmZWF0dXJlIiwgdmFsdWVzX3RvID0gInZhbHVlIikKYGBgCgpgYGB7cn0KIyBTdGVwIDI6IENyZWF0ZSBjaXJjdWxhciBiYXJwbG90CmdncGxvdCh0b3BfMjAyM19mZWF0dXJlcywgYWVzKHggPSBmZWF0dXJlLCB5ID0gdmFsdWUsIGZpbGwgPSBzb25nLmFydGlzdCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgY29vcmRfcG9sYXIoKSArCiAgbGFicyh0aXRsZSA9ICJBdWRpbyBGZWF0dXJlIE1ldHJpY3MgZm9yIFRvcCA1IFNvbmdzIGluIDIwMjMiLAogICAgICAgeCA9ICIiLAogICAgICAgeSA9ICIiLAogICAgICAgZmlsbCA9ICJTb25nIC0gQXJ0aXN0IikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIikpCmBgYAoKYGBge3J9CnllYXJseV90b3AzX3NvbmdzIDwtIHNwb3RpZnkgJT4lCiAgZ3JvdXBfYnkocmVsZWFzZWRfeWVhcikgJT4lCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc3RyZWFtcywgbiA9IDMsIHdpdGhfdGllcyA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQoKVmlldyh5ZWFybHlfdG9wM19zb25ncykKYGBgCgpgYGB7cn0KIyBGaWx0ZXIgZm9yIDIwMjMgdG9wIDMgc29uZ3MgZnJvbSB5b3VyIHByZXZpb3VzbHkgZmlsdGVyZWQgZGF0YQp0b3AzXzIwMjMgPC0geWVhcmx5X3RvcDNfc29uZ3MgJT4lCiAgZmlsdGVyKHJlbGVhc2VkX3llYXIgPT0gMjAyMykKCmhlYWQodG9wM18yMDIzKQpgYGAKCmBgYHtyfQojIE5vdyBleHBsaWNpdGx5IGNhbGwgZHBseXI6OnNlbGVjdCgpIHRvIGF2b2lkIG1hc2tpbmcKdG9wM18yMDIzX2ZlYXR1cmVzIDwtIHRvcDNfMjAyMyAlPiUKICBkcGx5cjo6c2VsZWN0KAogICAgc29uZy5hcnRpc3QsCiAgICBicG0sCiAgICBgZGFuY2VhYmlsaXR5Xy5gLAogICAgYHNwZWVjaGluZXNzXy5gLAogICAgYGVuZXJneV8uYCwKICAgIGBhY291c3RpY25lc3NfLmAKICApICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gLXNvbmcuYXJ0aXN0LCBuYW1lc190byA9ICJmZWF0dXJlIiwgdmFsdWVzX3RvID0gInZhbHVlIikKYGBgCmBgYHtyfQpoZWFkKHRvcDNfMjAyMykKYGBgCgpgYGB7cn0KIyBTdGVwIDI6IENyZWF0ZSBjaXJjdWxhciBiYXJwbG90CmdncGxvdCh0b3AzXzIwMjNfZmVhdHVyZXMsIGFlcyh4ID0gZmVhdHVyZSwgeSA9IHZhbHVlLCBmaWxsID0gc29uZy5hcnRpc3QpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGNvb3JkX3BvbGFyKCkgKwogIGxhYnModGl0bGUgPSAiQXVkaW8gRmVhdHVyZSBNZXRyaWNzIGZvciBUb3AgNSBTb25ncyBpbiAyMDIzIiwKICAgICAgIHggPSAiIiwKICAgICAgIHkgPSAiIiwKICAgICAgIGZpbGwgPSAiVHJhY2sgTmFtZSIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpKQpgYGAKCmBgYHtyfQojIExvYWQgY2xlYW4gbGlicmFyaWVzIChmb3JjZSByZWxvYWRpbmcgaWYgbmVlZGVkKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeSh0aWR5cikKbGlicmFyeShmbXNiKQpsaWJyYXJ5KHNjYWxlcykKYGBgCgpgYGB7cn0KIyBDbGVhbiBjb2x1bW4gbmFtZXMgdXNpbmcgYmFja3RpY2tzIGV4cGxpY2l0bHkKdG9wXzIwMjNfY2xlYW4gPC0gdG9wXzIwMjMgJT4lCiAgZHBseXI6OnJlbmFtZSgKICAgIGRhbmNlYWJpbGl0eSA9IGBkYW5jZWFiaWxpdHlfLmAsCiAgICBzcGVlY2hpbmVzcyA9IGBzcGVlY2hpbmVzc18uYCwKICAgIGVuZXJneSA9IGBlbmVyZ3lfLmAsCiAgICBhY291c3RpY25lc3MgPSBgYWNvdXN0aWNuZXNzXy5gCiAgKQoKIyBTZWxlY3Qgb25seSByZWxldmFudCBjb2x1bW5zCnJhZGFyX2RhdGEgPC0gZHBseXI6OnNlbGVjdCh0b3BfMjAyM19jbGVhbiwgc29uZy5hcnRpc3QsIGJwbSwgZGFuY2VhYmlsaXR5LCBzcGVlY2hpbmVzcywgZW5lcmd5LCBhY291c3RpY25lc3MpCgojIE5vcm1hbGl6ZSB0aGUgbWV0cmljcyB0byByYW5nZSBbMCwgMV0KcmFkYXJfZGF0YV9ub3JtIDwtIHJhZGFyX2RhdGEgJT4lCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSAmICFzb25nLmFydGlzdCwgfiBzY2FsZXM6OnJlc2NhbGUoLngsIHRvID0gYygwLCAxKSkpKQpgYGAKCmBgYHtyfQojIENyZWF0ZSBtYXggYW5kIG1pbiByb3dzIGZvciByZXF1aXJlZCByYWRhciBzdHJ1Y3R1cmUKbWF4X21pbiA8LSBkYXRhLmZyYW1lKAogIGJwbSA9IDEsIGRhbmNlYWJpbGl0eSA9IDEsIHNwZWVjaGluZXNzID0gMSwgZW5lcmd5ID0gMSwgYWNvdXN0aWNuZXNzID0gMSwKICByb3cubmFtZXMgPSBjKCJNYXgiKQopICU+JQogIGJpbmRfcm93cyhkYXRhLmZyYW1lKAogICAgYnBtID0gMCwgZGFuY2VhYmlsaXR5ID0gMCwgc3BlZWNoaW5lc3MgPSAwLCBlbmVyZ3kgPSAwLCBhY291c3RpY25lc3MgPSAwLAogICAgcm93Lm5hbWVzID0gYygiTWluIikKICApKQoKIyBBZGQgdGhlIHNvbmcgZGF0YSB3aXRoIHJvd25hbWVzIGFzIHNvbmcgdGl0bGVzCnJhZGFyX21hdHJpeCA8LSBiaW5kX3Jvd3MoCiAgbWF4X21pbiwKICByYWRhcl9kYXRhX25vcm0gJT4lIGNvbHVtbl90b19yb3duYW1lcygic29uZy5hcnRpc3QiKQopCmBgYAoKYGBge3J9CiMgQXNzaWduIGNvbG9ycyBwZXIgc29uZwpjb2xvcnNfYm9yZGVyIDwtIHJhaW5ib3cobnJvdyhyYWRhcl9tYXRyaXgpIC0gMikKY29sb3JzX2luIDwtIGFkanVzdGNvbG9yKGNvbG9yc19ib3JkZXIsIGFscGhhLmYgPSAwLjI1KQoKIyBQbG90CmZtc2I6OnJhZGFyY2hhcnQoCiAgcmFkYXJfbWF0cml4LAogIGF4aXN0eXBlID0gMSwKICBwY29sID0gY29sb3JzX2JvcmRlciwKICBwZmNvbCA9IGNvbG9yc19pbiwKICBwbHdkID0gMiwKICBwbHR5ID0gMSwKICBjZ2xjb2wgPSAiZ3JleSIsCiAgY2dsdHkgPSAxLAogIGF4aXNsYWJjb2wgPSAiZ3JleSIsCiAgY2F4aXNsYWJlbHMgPSBzZXEoMCwgMSwgMC4yKSwKICBjZ2x3ZCA9IDAuOCwKICB2bGNleCA9IDAuOSwKICB0aXRsZSA9ICJUb3AgNSBTb25ncyBpbiAyMDIzIOKAlCBBdWRpbyBGZWF0dXJlcyBSYWRhciBDaGFydCIKKQoKbGVnZW5kKAogICJ0b3ByaWdodCIsCiAgbGVnZW5kID0gcm93bmFtZXMocmFkYXJfbWF0cml4KVstYygxLCAyKV0sCiAgYnR5ID0gIm4iLAogIHBjaCA9IDIwLAogIGNvbCA9IGNvbG9yc19ib3JkZXIsCiAgdGV4dC5jb2wgPSAiYmxhY2siLAogIGNleCA9IDAuOAopCmBgYAoKYGBge3J9CgpgYGAKCmBgYHtyfQojIENsZWFuIGNvbHVtbiBuYW1lcyB1c2luZyBiYWNrdGlja3MgZXhwbGljaXRseQp0b3AzXzIwMjNfY2xlYW4gPC0gdG9wM18yMDIzICU+JQogIGRwbHlyOjpyZW5hbWUoCiAgICBkYW5jZWFiaWxpdHkgPSBgZGFuY2VhYmlsaXR5Xy5gLAogICAgc3BlZWNoaW5lc3MgPSBgc3BlZWNoaW5lc3NfLmAsCiAgICBlbmVyZ3kgPSBgZW5lcmd5Xy5gLAogICAgYWNvdXN0aWNuZXNzID0gYGFjb3VzdGljbmVzc18uYAogICkKCiMgU2VsZWN0IG9ubHkgcmVsZXZhbnQgY29sdW1ucwpyYWRhcl9kYXRhMyA8LSBkcGx5cjo6c2VsZWN0KHRvcDNfMjAyM19jbGVhbiwgc29uZy5hcnRpc3QsIGJwbSwgZGFuY2VhYmlsaXR5LCBzcGVlY2hpbmVzcywgZW5lcmd5LCBhY291c3RpY25lc3MpCgojIE5vcm1hbGl6ZSB0aGUgbWV0cmljcyB0byByYW5nZSBbMCwgMV0KcmFkYXJfZGF0YV9ub3JtMyA8LSByYWRhcl9kYXRhMyAlPiUKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpICYgIXNvbmcuYXJ0aXN0LCB+IHNjYWxlczo6cmVzY2FsZSgueCwgdG8gPSBjKDAsIDEpKSkpCmBgYAoKYGBge3J9CiMgQ3JlYXRlIG1heCBhbmQgbWluIHJvd3MgZm9yIHJlcXVpcmVkIHJhZGFyIHN0cnVjdHVyZQptYXhfbWluMyA8LSBkYXRhLmZyYW1lKAogIGJwbSA9IDEsIGRhbmNlYWJpbGl0eSA9IDEsIHNwZWVjaGluZXNzID0gMSwgZW5lcmd5ID0gMSwgYWNvdXN0aWNuZXNzID0gMSwKICByb3cubmFtZXMgPSBjKCJNYXgiKQopICU+JQogIGJpbmRfcm93cyhkYXRhLmZyYW1lKAogICAgYnBtID0gMCwgZGFuY2VhYmlsaXR5ID0gMCwgc3BlZWNoaW5lc3MgPSAwLCBlbmVyZ3kgPSAwLCBhY291c3RpY25lc3MgPSAwLAogICAgcm93Lm5hbWVzID0gYygiTWluIikKICApKQoKIyBBZGQgdGhlIHNvbmcgZGF0YSB3aXRoIHJvd25hbWVzIGFzIHNvbmcgdGl0bGVzCnJhZGFyX21hdHJpeDMgPC0gYmluZF9yb3dzKAogIG1heF9taW4zLAogIHJhZGFyX2RhdGFfbm9ybTMgJT4lIGNvbHVtbl90b19yb3duYW1lcygic29uZy5hcnRpc3QiKQopCmBgYAoKYGBge3J9CiMgQXNzaWduIGNvbG9ycyBwZXIgc29uZwpjb2xvcnNfYm9yZGVyIDwtIHJhaW5ib3cobnJvdyhyYWRhcl9tYXRyaXgpIC0gMikKY29sb3JzX2luIDwtIGFkanVzdGNvbG9yKGNvbG9yc19ib3JkZXIsIGFscGhhLmYgPSAwLjI1KQoKIyBQbG90CmZtc2I6OnJhZGFyY2hhcnQoCiAgcmFkYXJfbWF0cml4MywKICBheGlzdHlwZSA9IDEsCiAgcGNvbCA9IGNvbG9yc19ib3JkZXIsCiAgcGZjb2wgPSBjb2xvcnNfaW4sCiAgcGx3ZCA9IDIsCiAgcGx0eSA9IDEsCiAgY2dsY29sID0gImdyZXkiLAogIGNnbHR5ID0gMSwKICBheGlzbGFiY29sID0gImdyZXkiLAogIGNheGlzbGFiZWxzID0gc2VxKDAsIDEsIDAuMiksCiAgY2dsd2QgPSAwLjgsCiAgdmxjZXggPSAwLjksCiAgdGl0bGUgPSAiVG9wIDMgU29uZ3MgaW4gMjAyMyDigJQgQXVkaW8gRmVhdHVyZXMgUmFkYXIgQ2hhcnQiCikKCmxlZ2VuZCgKICAidG9wcmlnaHQiLAogIGxlZ2VuZCA9IHJvd25hbWVzKHJhZGFyX21hdHJpeDMpWy1jKDEsIDIpXSwKICBidHkgPSAibiIsCiAgcGNoID0gMjAsCiAgY29sID0gY29sb3JzX2JvcmRlciwKICB0ZXh0LmNvbCA9ICJibGFjayIsCiAgY2V4ID0gMC44LAogIGluc2V0ID0gYygtMC4xLCAwKSAjIE1vdmVzIHRoZSBsZWdlbmQgdG8gdGhlIHJpZ2h0IChuZWdhdGl2ZSB4LWluc2V0KQopCmBgYAoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZm1zYikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoc2hpbnkpCmBgYAoKYGBge3J9CiMgVUkgLS0tLQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiUmFkYXIgQ2hhcnQgb2YgVG9wIDMgU3BvdGlmeSBTb25ncyBieSBZZWFyIiksCiAgCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgc2VsZWN0SW5wdXQoInNlbGVjdGVkX3llYXIiLCAiQ2hvb3NlIGEgWWVhcjoiLAogICAgICAgICAgICAgICAgICBjaG9pY2VzID0gTlVMTCkKICAgICksCiAgICAKICAgIG1haW5QYW5lbCgKICAgICAgcGxvdE91dHB1dCgicmFkYXJQbG90IikKICAgICkKICApCikKCiMgU2VydmVyIC0tLS0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAKICAjIFBvcHVsYXRlIGRyb3Bkb3duIHdpdGggYXZhaWxhYmxlIHllYXJzCiAgb2JzZXJ2ZSh7CiAgICB1cGRhdGVTZWxlY3RJbnB1dChzZXNzaW9uLCAic2VsZWN0ZWRfeWVhciIsCiAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gc29ydCh1bmlxdWUoc3BvdGlmeSRyZWxlYXNlZF95ZWFyKSwgZGVjcmVhc2luZyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQgPSBtYXgoc3BvdGlmeSRyZWxlYXNlZF95ZWFyLCBuYS5ybSA9IFRSVUUpKQogIH0pCiAgCiAgIyBSZWFjdGl2ZTogY3JlYXRlIHJhZGFyIG1hdHJpeCBmb3Igc2VsZWN0ZWQgeWVhcgogIHJhZGFyX21hdHJpeDMgPC0gcmVhY3RpdmUoewogICAgcmVxKGlucHV0JHNlbGVjdGVkX3llYXIpCiAgICAKICAgICMgVG9wIDMgc29uZ3MgZm9yIHNlbGVjdGVkIHllYXIKICAgIHRvcDNfeWVhciA8LSBzcG90aWZ5ICU+JQogICAgICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSBpbnB1dCRzZWxlY3RlZF95ZWFyKSAlPiUKICAgICAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc3RyZWFtcywgbiA9IDMsIHdpdGhfdGllcyA9IEZBTFNFKSAlPiUKICAgICAgZHBseXI6OnJlbmFtZSgKICAgICAgICBkYW5jZWFiaWxpdHkgPSBgZGFuY2VhYmlsaXR5Xy5gLAogICAgICAgIHNwZWVjaGluZXNzID0gYHNwZWVjaGluZXNzXy5gLAogICAgICAgIGVuZXJneSA9IGBlbmVyZ3lfLmAsCiAgICAgICAgYWNvdXN0aWNuZXNzID0gYGFjb3VzdGljbmVzc18uYAogICAgICApICU+JQogICAgICBkcGx5cjo6c2VsZWN0KHNvbmcuYXJ0aXN0LCBicG0sIGRhbmNlYWJpbGl0eSwgc3BlZWNoaW5lc3MsIGVuZXJneSwgYWNvdXN0aWNuZXNzKQogICAgCiAgICAjIE5vcm1hbGl6ZSBudW1lcmljIGNvbHVtbnMgdG8gMOKAkzEKICAgIHJhZGFyX2RhdGFfbm9ybTMgPC0gdG9wM195ZWFyICU+JQogICAgICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+IHNjYWxlczo6cmVzY2FsZSgueCwgdG8gPSBjKDAsIDEpKSkpCiAgICAKICAgIGlmIChucm93KHJhZGFyX2RhdGFfbm9ybTMpIDwgMSkgcmV0dXJuKE5VTEwpCiAgICAKICAgICMgQ3JlYXRlIG1heC9taW4gcm93cwogICAgbWF4X21pbjMgPC0gZGF0YS5mcmFtZSgKICAgICAgYnBtID0gMSwgZGFuY2VhYmlsaXR5ID0gMSwgc3BlZWNoaW5lc3MgPSAxLCBlbmVyZ3kgPSAxLCBhY291c3RpY25lc3MgPSAxLAogICAgICByb3cubmFtZXMgPSBjKCJNYXgiKQogICAgKSAlPiUKICAgICAgYmluZF9yb3dzKGRhdGEuZnJhbWUoCiAgICAgICAgYnBtID0gMCwgZGFuY2VhYmlsaXR5ID0gMCwgc3BlZWNoaW5lc3MgPSAwLCBlbmVyZ3kgPSAwLCBhY291c3RpY25lc3MgPSAwLAogICAgICAgIHJvdy5uYW1lcyA9IGMoIk1pbiIpCiAgICAgICkpCiAgICAKICAgICMgQmluZCBub3JtYWxpemVkIGRhdGEsIHNldCByb3duYW1lcyB0byBzb25nLmFydGlzdAogICAgZmluYWxfbWF0cml4IDwtIGJpbmRfcm93cygKICAgICAgbWF4X21pbjMsCiAgICAgIHJhZGFyX2RhdGFfbm9ybTMgJT4lIGNvbHVtbl90b19yb3duYW1lcygic29uZy5hcnRpc3QiKQogICAgKQogICAgCiAgICByZXR1cm4oZmluYWxfbWF0cml4KQogIH0pCiAgCiAgIyBQbG90IG91dHB1dCAtLS0tCm91dHB1dCRyYWRhclBsb3QgPC0gcmVuZGVyUGxvdCh7CiAgbWF0cml4IDwtIHJhZGFyX21hdHJpeDMoKQogIHJlcShtYXRyaXgpCgogICMgQ29sb3JzCiAgY29sb3JzX2JvcmRlciA8LSByYWluYm93KG5yb3cobWF0cml4KSAtIDIpCiAgY29sb3JzX2luIDwtIGFkanVzdGNvbG9yKGNvbG9yc19ib3JkZXIsIGFscGhhLmYgPSAwLjI1KQoKICAjIFNldCB1cCBhIHR3by1yb3cgbGF5b3V0OiBjaGFydCBvbiB0b3AsIGxlZ2VuZCBiZWxvdwogIGxheW91dChtYXRyaXgoYygxLCAyKSwgbnJvdyA9IDIpLCBoZWlnaHRzID0gYyg0LCAxKSkgICMgVG9wOiA0eCBoZWlnaHQsIEJvdHRvbTogMXggaGVpZ2h0CgogICMgVG9wOiBSYWRhciBDaGFydAogIHBhcihtYXIgPSBjKDIsIDIsIDQsIDIpKSAgIyByZWFzb25hYmxlIG1hcmdpbnMKICBmbXNiOjpyYWRhcmNoYXJ0KAogICAgbWF0cml4LAogICAgYXhpc3R5cGUgPSAxLAogICAgcGNvbCA9IGNvbG9yc19ib3JkZXIsCiAgICBwZmNvbCA9IGNvbG9yc19pbiwKICAgIHBsd2QgPSAyLAogICAgcGx0eSA9IDEsCiAgICBjZ2xjb2wgPSAiZ3JleSIsCiAgICBjZ2x0eSA9IDEsCiAgICBheGlzbGFiY29sID0gImdyZXkiLAogICAgY2F4aXNsYWJlbHMgPSBzZXEoMCwgMSwgMC4yKSwKICAgIGNnbHdkID0gMC44LAogICAgdmxjZXggPSAwLjksCiAgICB0aXRsZSA9IHBhc3RlKCJUb3AgMyBTb25ncyBpbiIsIGlucHV0JHNlbGVjdGVkX3llYXIsICLigJQgQXVkaW8gRmVhdHVyZXMgUmFkYXIgQ2hhcnQiKQogICkKCiAgIyBCb3R0b206IExlZ2VuZAogIHBhcihtYXIgPSBjKDAsIDAsIDAsIDApKSAgIyBubyBtYXJnaW5zCiAgcGxvdC5uZXcoKQogIGxlZ2VuZCgKICAgICJjZW50ZXIiLAogICAgbGVnZW5kID0gcm93bmFtZXMobWF0cml4KVstYygxLCAyKV0sCiAgICBidHkgPSAibiIsCiAgICBwY2ggPSAyMCwKICAgIGNvbCA9IGNvbG9yc19ib3JkZXIsCiAgICB0ZXh0LmNvbCA9ICJibGFjayIsCiAgICBjZXggPSAwLjksCiAgICBuY29sID0gMSwgICMgWW91IGNhbiBjaGFuZ2UgdG8gMisgaWYgeW91IHdhbnQgY29sdW1ucwogICAgeHBkID0gVFJVRQogICkKfSkKfQojIFJ1biB0aGUgYXBwIC0tLS0Kc2hpbnlBcHAodWksIHNlcnZlcikKCmBgYAoKYGBge3J9CgpgYGAKCmBgYHtyfQojIFNlbGVjdCBvbmx5IG51bWVyaWMgY29sdW1ucwpudW1lcmljX2NvbHMgPC0gc3BvdGlmeSAlPiUKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpCgpudW1lcmljX2NvbHMKYGBgCgpgYGB7cn0KIyBDYWxjdWxhdGUgY29ycmVsYXRpb24gb2YgYWxsIG51bWVyaWMgY29sdW1ucyB3aXRoICdzdHJlYW1zJwpjb3JyZWxhdGlvbnMgPC0gY29yKG51bWVyaWNfY29scywgdXNlID0gImNvbXBsZXRlLm9icyIpCmNvcnJlbGF0aW9ucwpgYGAKCmBgYHtyfQojIEV4dHJhY3QganVzdCB0aGUgY29ycmVsYXRpb25zIHdpdGggJ3N0cmVhbXMnCmNvcl93aXRoX3N0cmVhbXMgPC0gY29ycmVsYXRpb25zWyJzdHJlYW1zIiwgXQpjb3Jfd2l0aF9zdHJlYW1zCmBgYAoKYGBge3J9CiMgU29ydCBhbmQgdmlldwpzb3J0KGNvcl93aXRoX3N0cmVhbXMsIGRlY3JlYXNpbmcgPSBUUlVFKQoKYGBgCgpgYGB7cn0KbW9kZWwgPC0gbG0oc3RyZWFtcyB+IGluX3Nwb3RpZnlfcGxheWxpc3RzICsgaW5fZGVlemVyX3BsYXlsaXN0cyArIGluX2FwcGxlX3BsYXlsaXN0cyArIGRhbmNlYWJpbGl0eV8uICsgZW5lcmd5Xy4gKyB2YWxlbmNlXy4sIGRhdGEgPSBzcG90aWZ5KQpzdW1tYXJ5KG1vZGVsKQpgYGAKCmBgYHtyfQojIFJlZml0dGluZyB0aGUgbW9kZWwgd2l0aCBvbmx5IHNpZ25pZmljYW50IHByZWRpY3RvcnMKcmVmaW5lZF9tb2RlbCA8LSBsbShzdHJlYW1zIH4gaW5fc3BvdGlmeV9wbGF5bGlzdHMgKyBpbl9kZWV6ZXJfcGxheWxpc3RzICsgaW5fYXBwbGVfcGxheWxpc3RzLCBkYXRhID0gc3BvdGlmeSkKCiMgU3VtbWFyeSBvZiB0aGUgcmVmaW5lZCBtb2RlbApzdW1tYXJ5KHJlZmluZWRfbW9kZWwpCmBgYAoKYGBge3J9CiMgUmVmaXQgdGhlIG1vZGVsIHdpdGggY29tcGxldGUgY2FzZXMgb25seQpkYXRhX2NvbXBsZXRlIDwtIHNwb3RpZnkgJT4lCiAgc2VsZWN0KHN0cmVhbXMsIGluX3Nwb3RpZnlfcGxheWxpc3RzLCBpbl9kZWV6ZXJfcGxheWxpc3RzLCBpbl9hcHBsZV9wbGF5bGlzdHMpICU+JQogIG5hLm9taXQoKQoKbW9kZWwgPC0gbG0oc3RyZWFtcyB+IGluX3Nwb3RpZnlfcGxheWxpc3RzICsgaW5fZGVlemVyX3BsYXlsaXN0cyArIGluX2FwcGxlX3BsYXlsaXN0cywgZGF0YSA9IGRhdGFfY29tcGxldGUpCgojIEFkZCBwcmVkaWN0aW9ucyB0byB0aGUgY29tcGxldGUgZGF0YQoKIyBQcmVkaWN0IHN0cmVhbXMKcHJlZGljdGVkX3N0cmVhbXMgPC0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IGRhdGFfY29tcGxldGUpCgojIEFkZCBwcmVkaWN0aW9ucyB0byB0aGUgZGF0YSBmcmFtZQpkYXRhX2NvbXBsZXRlJHByZWRpY3RlZF9zdHJlYW1zIDwtIHByZWRpY3RlZF9zdHJlYW1zCmRhdGFfY29tcGxldGUKYGBgCgpgYGB7cn0KIyBQbG90IGFjdHVhbCB2cyBwcmVkaWN0ZWQKZ2dwbG90KGRhdGFfY29tcGxldGUsIGFlcyh4ID0gc3RyZWFtcywgeSA9IHByZWRpY3RlZF9zdHJlYW1zKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gInN0ZWVsYmx1ZSIpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIkFjdHVhbCB2cyBQcmVkaWN0ZWQgU3RyZWFtcyIsCiAgICAgICB4ID0gIkFjdHVhbCBTdHJlYW1zIiwKICAgICAgIHkgPSAiUHJlZGljdGVkIFN0cmVhbXMiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CiMgUGxvdCByZXNpZHVhbHMKcmVzaWR1YWxzIDwtIG1vZGVsJHJlc2lkdWFscwpWaWV3KGRhdGFfY29tcGxldGUpCgpnZ3Bsb3QoZGF0YV9jb21wbGV0ZSwgYWVzKHggPSBwcmVkaWN0ZWRfc3RyZWFtcywgeSA9IHJlc2lkdWFscykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJkYXJrb3JhbmdlIikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFsIFBsb3QiLAogICAgICAgeCA9ICJQcmVkaWN0ZWQgU3RyZWFtcyIsCiAgICAgICB5ID0gIlJlc2lkdWFscyIpICsKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKYGBge3J9CiMgU3RlcCAxOiBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcwpsaWJyYXJ5KGNhcmV0KQoKIyBTdGVwIDI6IFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTIzKQoKIyBTdGVwIDM6IERlZmluZSB0cmFpbmluZyBjb250cm9sIGZvciAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24KdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApCgojIFN0ZXAgNDogRGVmaW5lIHRoZSBtb2RlbCBmb3JtdWxhIChzYW1lIHByZWRpY3RvcnMgYXMgYmVmb3JlKQptb2RlbF9mb3JtdWxhIDwtIHN0cmVhbXMgfiBpbl9zcG90aWZ5X3BsYXlsaXN0cyArIGluX2RlZXplcl9wbGF5bGlzdHMgKyBpbl9hcHBsZV9wbGF5bGlzdHMKCiMgU3RlcCA1OiBGaXQgdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIGNhcmV0Ojp0cmFpbigpCmN2X21vZGVsIDwtIHRyYWluKAogIG1vZGVsX2Zvcm11bGEsCiAgZGF0YSA9IGRhdGFfY29tcGxldGUsCiAgbWV0aG9kID0gImxtIiwKICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sCikKCiMgU3RlcCA2OiBSZXZpZXcgY3Jvc3MtdmFsaWRhdGlvbiByZXN1bHRzCnByaW50KGN2X21vZGVsKQoKIyBPcHRpb25hbDogUGxvdCBwcmVkaWN0aW9ucyB2cy4gYWN0dWFscyBhZ2FpbiB1c2luZyBjdl9tb2RlbCRmaW5hbE1vZGVsIGlmIGRlc2lyZWQKCmBgYAoKYGBge3J9CiMgRml0IHRoZSBmaW5hbCBtb2RlbCBvbiBmdWxsIGRhdGEKZmluYWxfbW9kZWwgPC0gdHJhaW4oCiAgc3RyZWFtcyB+IGluX3Nwb3RpZnlfcGxheWxpc3RzICsgaW5fZGVlemVyX3BsYXlsaXN0cyArIGluX2FwcGxlX3BsYXlsaXN0cywKICBkYXRhID0gZGF0YV9jb21wbGV0ZSwKICBtZXRob2QgPSAibG0iCikKCiMgVmlldyBmaW5hbCBjb2VmZmljaWVudHMKY29lZihmaW5hbF9tb2RlbCRmaW5hbE1vZGVsKQoKYGBgCgpgYGB7cn0KI0NyZWF0ZSBhIG5ldyBkYXRhIGZyYW1lIHdpdGggcHJlZGljdG9yIHZhbHVlcwojIFJlcGxhY2UgdGhlc2UgbnVtYmVycyB3aXRoIHlvdXIgYWN0dWFsIGlucHV0IHZhbHVlcwpuZXdfaW5wdXQgPC0gZGF0YS5mcmFtZSgKICBpbl9zcG90aWZ5X3BsYXlsaXN0cyA9IDI1MDAsCiAgaW5fZGVlemVyX3BsYXlsaXN0cyA9IDUwLAogIGluX2FwcGxlX3BsYXlsaXN0cyA9IDI1MAopCgojIDMuIFByZWRpY3Qgc3RyZWFtcyBiYXNlZCBvbiBuZXcgaW5wdXRzCnByZWRpY3RlZF9zdHJlYW1zIDwtIHByZWRpY3QoY3ZfbW9kZWwsIG5ld2RhdGEgPSBuZXdfaW5wdXQpCgojIFZpZXcgcHJlZGljdGlvbgpwcmVkaWN0ZWRfc3RyZWFtcwoKYGBgCgpgYGB7cn0KIyBMb2FkIHJlcXVpcmVkIHBhY2thZ2VzCmxpYnJhcnkoc2hpbnkpCgojIERlZmluZSBVSQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiUHJlZGljdCBTb25nIFN0cmVhbXMiKSwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICBudW1lcmljSW5wdXQoInNwb3RpZnkiLCAiU3BvdGlmeSBQbGF5bGlzdHM6IiwgdmFsdWUgPSA1MDAwLCBtaW4gPSAwKSwKICAgICAgbnVtZXJpY0lucHV0KCJkZWV6ZXIiLCAiRGVlemVyIFBsYXlsaXN0czoiLCB2YWx1ZSA9IDEwMDAsIG1pbiA9IDApLAogICAgICBudW1lcmljSW5wdXQoImFwcGxlIiwgIkFwcGxlIFBsYXlsaXN0czoiLCB2YWx1ZSA9IDIwMDAsIG1pbiA9IDApLAogICAgICBhY3Rpb25CdXR0b24oInByZWRpY3QiLCAiUHJlZGljdCBTdHJlYW1zIikKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIGgzKCJQcmVkaWN0ZWQgU3RyZWFtczoiKSwKICAgICAgdmVyYmF0aW1UZXh0T3V0cHV0KCJwcmVkaWN0aW9uIikKICAgICkKICApCikKCiMgRGVmaW5lIHNlcnZlciBsb2dpYwpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewoKICAjIFJlYWN0aXZlIHByZWRpY3Rpb24KICBvYnNlcnZlRXZlbnQoaW5wdXQkcHJlZGljdCwgewogICAgbmV3X2lucHV0IDwtIGRhdGEuZnJhbWUoCiAgICAgIGluX3Nwb3RpZnlfcGxheWxpc3RzID0gaW5wdXQkc3BvdGlmeSwKICAgICAgaW5fZGVlemVyX3BsYXlsaXN0cyA9IGlucHV0JGRlZXplciwKICAgICAgaW5fYXBwbGVfcGxheWxpc3RzID0gaW5wdXQkYXBwbGUKICAgICkKCiAgICBwcmVkaWN0ZWQgPC0gcHJlZGljdChjdl9tb2RlbCwgbmV3ZGF0YSA9IG5ld19pbnB1dCkKCiAgICBvdXRwdXQkcHJlZGljdGlvbiA8LSByZW5kZXJUZXh0KHsKICAgICAgZm9ybWF0KHJvdW5kKHByZWRpY3RlZCwgMCksIGJpZy5tYXJrID0gIiwiKQogICAgfSkKICB9KQp9CgojIFJ1biB0aGUgYXBwbGljYXRpb24gCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKCmBgYAoKYGBge3J9Cj9jdl9tb2RlbApjdl9tb2RlbApgYGAKCmBgYHtyfQojIEFzc3VtZSBgZmluYWxfbW9kZWxgIGlzIGFscmVhZHkgdHJhaW5lZCB3aXRoIGxtKCkKY29sU3Vtcyhpcy5uYShkYXRhX2NvbXBsZXRlKSkKYGBgCgpgYGB7cn0KCmBgYAoKYGBge3J9CiMgVGhlbiB5b3UgbmVlZCB0byBleHRyYWN0IHRoZSBmaW5hbCBsaW5lYXIgbW9kZWwgZnJvbSB0aGUgYHRyYWluYCBvYmplY3QKIyBiZWZvcmUgdXNpbmcgaXQgZm9yIHByZWRpY3Rpb24gd2l0aCBjb25maWRlbmNlIGFuZCBwcmVkaWN0aW9uIGludGVydmFscwoKbG1fbW9kZWwgPC0gY3ZfbW9kZWwkZmluYWxNb2RlbAoKIyBOb3cgeW91IGNhbiBzYWZlbHkgdXNlIHByZWRpY3Qgd2l0aCBpbnRlcnZhbCA9ICJjb25maWRlbmNlIiBhbmQgInByZWRpY3Rpb24iCnByZWRfY29uZiA8LSBwcmVkaWN0KGxtX21vZGVsLCBuZXdkYXRhID0gZGF0YV9jb21wbGV0ZSwgaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIpCnByZWRfcHJlZCA8LSBwcmVkaWN0KGxtX21vZGVsLCBuZXdkYXRhID0gZGF0YV9jb21wbGV0ZSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIpCgojIENvbWJpbmUgZXZlcnl0aGluZyBpbnRvIGEgZGF0YSBmcmFtZQpwbG90X2RhdGEgPC0gZGF0YV9jb21wbGV0ZSAlPiUKICBtdXRhdGUoCiAgICBwcmVkaWN0ZWRfc3RyZWFtcyA9IHByZWRfY29uZlssICJmaXQiXSwKICAgIGNvbmZfbG93ID0gcHJlZF9jb25mWywgImx3ciJdLAogICAgY29uZl9oaWdoID0gcHJlZF9jb25mWywgInVwciJdLAogICAgcHJlZF9sb3cgPSBwcmVkX3ByZWRbLCAibHdyIl0sCiAgICBwcmVkX2hpZ2ggPSBwcmVkX3ByZWRbLCAidXByIl0KICApCgojIFBsb3QKZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHggPSBzdHJlYW1zLCB5ID0gcHJlZGljdGVkX3N0cmVhbXMpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNiwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gY29uZl9sb3csIHltYXggPSBjb25mX2hpZ2gpLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGFscGhhID0gMC4zKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBwcmVkX2xvdywgeW1heCA9IHByZWRfaGlnaCksIGZpbGwgPSAib3JhbmdlIiwgYWxwaGEgPSAwLjIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiUHJlZGljdGVkIHZzIEFjdHVhbCBTdHJlYW1zIHdpdGggQ29uZmlkZW5jZSBhbmQgUHJlZGljdGlvbiBJbnRlcnZhbHMiLAogICAgeCA9ICJBY3R1YWwgU3RyZWFtcyIsCiAgICB5ID0gIlByZWRpY3RlZCBTdHJlYW1zIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgpgYGB7cn0KIyBGaXJzdCwgZW5zdXJlIHRoYXQgeW91IGhhdmUgeW91ciBwcmVkaWN0aW9ucyB3aXRoIGludGVydmFscyBzZXQgdXAgcHJvcGVybHkKCiMgRXh0cmFjdCB0aGUgbGluZWFyIG1vZGVsIGZyb20gY2FyZXQncyB0cmFpbiBvYmplY3QKbG1fbW9kZWwgPC0gY3ZfbW9kZWwkZmluYWxNb2RlbAoKIyBHZW5lcmF0ZSBwcmVkaWN0aW9ucyB3aXRoIGJvdGggY29uZmlkZW5jZSBhbmQgcHJlZGljdGlvbiBpbnRlcnZhbHMKcHJlZF9jb25mIDwtIHByZWRpY3QobG1fbW9kZWwsIG5ld2RhdGEgPSBkYXRhX2NvbXBsZXRlLCBpbnRlcnZhbCA9ICJjb25maWRlbmNlIikKcHJlZF9wcmVkIDwtIHByZWRpY3QobG1fbW9kZWwsIG5ld2RhdGEgPSBkYXRhX2NvbXBsZXRlLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIikKCiMgQ29tYmluZSBldmVyeXRoaW5nIGludG8gYSBkYXRhIGZyYW1lCnBsb3RfZGF0YSA8LSBkYXRhX2NvbXBsZXRlICU+JQogIG11dGF0ZSgKICAgIHByZWRpY3RlZF9zdHJlYW1zID0gcHJlZF9jb25mWywgImZpdCJdLAogICAgY29uZl9sb3cgPSBwcmVkX2NvbmZbLCAibHdyIl0sCiAgICBjb25mX2hpZ2ggPSBwcmVkX2NvbmZbLCAidXByIl0sCiAgICBwcmVkX2xvdyA9IHByZWRfcHJlZFssICJsd3IiXSwKICAgIHByZWRfaGlnaCA9IHByZWRfcHJlZFssICJ1cHIiXQogICkgJT4lCiAgYXJyYW5nZShzdHJlYW1zKSAgIyBzb3J0IGJ5IGFjdHVhbCBzdHJlYW1zIGZvciBzbW9vdGggcmliYm9ucwpgYGAKCmBgYHtyfQojIFBsb3Qgd2l0aCBnZ3Bsb3QyCmdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gcHJlZGljdGVkX3N0cmVhbXMsIHkgPSBzdHJlYW1zKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gImRhcmtibHVlIikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGNvbmZfbG93LCB5bWF4ID0gY29uZl9oaWdoKSwgZmlsbCA9ICJsaWdodGJsdWUiLCBhbHBoYSA9IDAuMykgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gcHJlZF9sb3csIHltYXggPSBwcmVkX2hpZ2gpLCBmaWxsID0gIm9yYW5nZSIsIGFscGhhID0gMC4yKSArCiAgbGFicygKICAgIHRpdGxlID0gIlByZWRpY3RlZCB2cyBBY3R1YWwgU3RyZWFtcyB3aXRoIENvbmZpZGVuY2UgYW5kIFByZWRpY3Rpb24gSW50ZXJ2YWxzIiwKICAgIHggPSAiUHJlZGljdGVkIFN0cmVhbXMiLAogICAgeSA9ICJBY3R1YWwgU3RyZWFtcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBQbG90IHdpdGggY29uZmlkZW5jZSBhbmQgcHJlZGljdGlvbiBpbnRlcnZhbHMgYXMgbGluZXMgKG5vIHNoYWRlZCByaWJib25zKQpnZ3Bsb3QocGxvdF9kYXRhLCBhZXMoeCA9IHByZWRpY3RlZF9zdHJlYW1zLCB5ID0gc3RyZWFtcykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gInNvbGlkIiwgY29sb3IgPSAiYmx1ZSIpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBjb25mX2xvdyksIGNvbG9yID0gInB1cnBsZSIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBjb25mX2hpZ2gpLCBjb2xvciA9ICJwdXJwbGUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZF9sb3cpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZF9oaWdoKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJBY3R1YWwgdnMgUHJlZGljdGVkIFN0cmVhbXMgd2l0aCBDb25maWRlbmNlIGFuZCBQcmVkaWN0aW9uIEludGVydmFsIExpbmVzIChSXjIgPSAwLjY4KSIsCiAgICB4ID0gIlByZWRpY3RlZCBTdHJlYW1zIiwKICAgIHkgPSAiQWN0dWFsIFN0cmVhbXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CmdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gaW5fc3BvdGlmeV9wbGF5bGlzdHMsIHkgPSBzdHJlYW1zKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZGljdGVkX3N0cmVhbXMpLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fcmliYm9uKGFlcyh5bWluID0gY29uZl9sb3csIHltYXggPSBjb25mX2hpZ2gpLCBhbHBoYSA9IDAuMikgKwogIGxhYnModGl0bGUgPSAiTW9kZWwgRml0IHdpdGggQ29uZmlkZW5jZSBJbnRlcnZhbCIsCiAgICAgICB5ID0gIlN0cmVhbXMiLCB4ID0gIlNwb3RpZnkgUGxheWxpc3RzIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIExvYWQgcmVxdWlyZWQgcGFja2FnZXMKbGlicmFyeShzaGlueSkKCiMgQ29tYmluZSBWaXN1YWxzL0RlZmluZSBVSQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiUHJlZGljdCBTcG90aWZ5IFNvbmcgU3RyZWFtcyIpLAogIHRhYnNldFBhbmVsKAogICAgdGFiUGFuZWwoIlZpc3VhbGl6ZSBieSBNb2RlIiwKICAgICAgc2lkZWJhckxheW91dCgKICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICBjaGVja2JveEdyb3VwSW5wdXQoInNlbGVjdGVkX21vZGVzIiwgIlNlbGVjdCBNb2RlKHMpOiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IHVuaXF1ZShzcG90aWZ5JG1vZGUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gdW5pcXVlKHNwb3RpZnkkbW9kZSkpCiAgICAgICAgKSwKICAgICAgICBtYWluUGFuZWwoCiAgICAgICAgICBwbG90T3V0cHV0KCJtb2RlUGxvdCIpCiAgICAgICAgKQogICAgICApCiAgICApLAogICAgdGFiUGFuZWwoIlByZWRpY3QgU3RyZWFtcyIsCiAgICAgIHNpZGViYXJMYXlvdXQoCiAgICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgICAgbnVtZXJpY0lucHV0KCJzcG90aWZ5IiwgIlNwb3RpZnkgUGxheWxpc3RzOiIsIHZhbHVlID0gNTAwMCwgbWluID0gMCksCiAgICAgICAgICBudW1lcmljSW5wdXQoImRlZXplciIsICJEZWV6ZXIgUGxheWxpc3RzOiIsIHZhbHVlID0gMTAwMCwgbWluID0gMCksCiAgICAgICAgICBudW1lcmljSW5wdXQoImFwcGxlIiwgIkFwcGxlIFBsYXlsaXN0czoiLCB2YWx1ZSA9IDIwMDAsIG1pbiA9IDApLAogICAgICAgICAgYWN0aW9uQnV0dG9uKCJwcmVkaWN0IiwgIlByZWRpY3QgU3RyZWFtcyIpCiAgICAgICAgKSwKICAgICAgICBtYWluUGFuZWwoCiAgICAgICAgICBoMygiUHJlZGljdGVkIFN0cmVhbXM6IiksCiAgICAgICAgICB2ZXJiYXRpbVRleHRPdXRwdXQoInByZWRpY3Rpb24iKQogICAgICAgICkKICAgICAgKQogICAgKQogICkKKQoKIyBEZWZpbmUgc2VydmVyIGxvZ2ljCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7CgogICMgUmVhY3RpdmUgcHJlZGljdGlvbgogIG9ic2VydmVFdmVudChpbnB1dCRwcmVkaWN0LCB7CiAgICBuZXdfaW5wdXQgPC0gZGF0YS5mcmFtZSgKICAgICAgaW5fc3BvdGlmeV9wbGF5bGlzdHMgPSBpbnB1dCRzcG90aWZ5LAogICAgICBpbl9kZWV6ZXJfcGxheWxpc3RzID0gaW5wdXQkZGVlemVyLAogICAgICBpbl9hcHBsZV9wbGF5bGlzdHMgPSBpbnB1dCRhcHBsZQogICAgKQoKICAgIHByZWRpY3RlZCA8LSBwcmVkaWN0KGZpbmFsX21vZGVsLCBuZXdkYXRhID0gbmV3X2lucHV0KQoKICAgIG91dHB1dCRwcmVkaWN0aW9uIDwtIHJlbmRlclRleHQoewogICAgICBmb3JtYXQocm91bmQocHJlZGljdGVkLCAwKSwgYmlnLm1hcmsgPSAiLCIpCiAgICB9KQogIH0pCgogIG91dHB1dCRtb2RlUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIHJlcShpbnB1dCRzZWxlY3RlZF9tb2RlcykKCiAgICBmaWx0ZXJlZF9kYXRhIDwtIHN1YnNldChzcG90aWZ5LCBtb2RlICVpbiUgaW5wdXQkc2VsZWN0ZWRfbW9kZXMpCgogICAgZ2dwbG90KGZpbHRlcmVkX2RhdGEsIGFlcyh4ID0gc3RyZWFtcywgeSA9IGluX3Nwb3RpZnlfcGxheWxpc3RzLCBjb2xvciA9IG1vZGUpKSArCiAgICAgIGdlb21fcG9pbnQoKSArCiAgICAgIGxhYnModGl0bGUgPSAiU3RyZWFtcyB2cyBQbGF5bGlzdCBNZXRyaWNzIGJ5IE1vZGUiLAogICAgICAgICAgIHggPSAiU3RyZWFtcyIsCiAgICAgICAgICAgeSA9ICJOdW1iZXIgaW4gU3BvdGlmeSBQbGF5bGlzdHMiKSArCiAgICAgIHRoZW1lX21pbmltYWwoKQogIH0pCn0KCiMgUnVuIHRoZSBhcHBsaWNhdGlvbiAKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQoKYGBgCgpgYGB7cn0KI3NpbmdsZSB2YXJpYWJsZSAKZ2dwbG90KHBsb3RfZGF0YSwgYWVzKHggPSBwcmVkaWN0ZWRfc3RyZWFtcywgeSA9IGluX3Nwb3RpZnlfcGxheWxpc3RzKSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSBzdHJlYW1zKSwgYWxwaGEgPSAwLjUpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBjb25mX2xvdywgeW1heCA9IGNvbmZfaGlnaCksIGFscGhhID0gMC4yKSArCiAgbGFicyh0aXRsZSA9ICJQcmVkaWN0aW9uIHdpdGggQ29uZmlkZW5jZSBJbnRlcnZhbHMiKQpgYGAKCmBgYHtyfQpwbG90KGxtX21vZGVsKQpgYGAKCmBgYHtyfQpwbG90KGxtX21vZGVsLCB3aGljaCA9IDUpIApgYGAKCmBgYHtyfQojIENhbGN1bGF0ZSBDb29rJ3MgRGlzdGFuY2UKY29va3NEIDwtIGNvb2tzLmRpc3RhbmNlKGxtX21vZGVsKQoKIyBTZXQgYSBjb21tb24gdGhyZXNob2xkICg0IC8gbikKdGhyZXNob2xkIDwtIDQgLyBucm93KGRhdGFfY29tcGxldGUpCgojIEZpbmQgaW5mbHVlbnRpYWwgcG9pbnRzCmluZmx1ZW50aWFsX3BvaW50cyA8LSB3aGljaChjb29rc0QgPiB0aHJlc2hvbGQpCiAKaW5mbHVlbnRpYWxfcG9pbnRzCmBgYAoKYGBge3J9CiMgQ3JlYXRlIGEgbmV3IGRhdGFzZXQgZXhjbHVkaW5nIGluZmx1ZW50aWFsIHJvd3MKZGF0YV9ub19pbmZsdWVudGlhbCA8LSBkYXRhX2NvbXBsZXRlWy1pbmZsdWVudGlhbF9wb2ludHMsIF0KZGF0YV9ub19pbmZsdWVudGlhbApgYGAKCmBgYHtyfQojIFJlZml0IHRoZSBtb2RlbCB1c2luZyBjYXJldCB3aXRoIGNyb3NzLXZhbGlkYXRpb24KY3ZfbW9kZWxfY2xlYW4gPC0gdHJhaW4oCiAgbW9kZWxfZm9ybXVsYSwKICBkYXRhID0gZGF0YV9ub19pbmZsdWVudGlhbCwKICBtZXRob2QgPSAibG0iLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkKKQpgYGAKCmBgYHtyfQpjdl9tb2RlbCRyZXN1bHRzICAjIE9yaWdpbmFsIG1vZGVsCmN2X21vZGVsX2NsZWFuJHJlc3VsdHMgICMgTW9kZWwgd2l0aG91dCBpbmZsdWVudGlhbCBwb2ludHMKYGBgCgpgYGB7cn0KbW9kZWxfZnVsbCA8LSBsbShtb2RlbF9mb3JtdWxhLCBkYXRhID0gZGF0YV9jb21wbGV0ZSkKbW9kZWxfcmVkdWNlZCA8LSBsbShtb2RlbF9mb3JtdWxhLCBkYXRhID0gZGF0YV9ub19pbmZsdWVudGlhbCkKCkFJQyhtb2RlbF9mdWxsLCBtb2RlbF9yZWR1Y2VkKQpCSUMobW9kZWxfZnVsbCwgbW9kZWxfcmVkdWNlZCkKYGBgCgpgYGB7cn0KIyBGaXJzdCwgZW5zdXJlIHRoYXQgeW91IGhhdmUgeW91ciBwcmVkaWN0aW9ucyB3aXRoIGludGVydmFscyBzZXQgdXAgcHJvcGVybHkKCiMgRXh0cmFjdCB0aGUgbGluZWFyIG1vZGVsIGZyb20gY2FyZXQncyB0cmFpbiBvYmplY3QKbG1fbW9kZWxfY2xlYW4gPC0gY3ZfbW9kZWxfY2xlYW4kZmluYWxNb2RlbAoKIyBHZW5lcmF0ZSBwcmVkaWN0aW9ucyB3aXRoIGJvdGggY29uZmlkZW5jZSBhbmQgcHJlZGljdGlvbiBpbnRlcnZhbHMKcHJlZF9jb25mX2NsZWFuIDwtIHByZWRpY3QobG1fbW9kZWxfY2xlYW4sIG5ld2RhdGEgPSBkYXRhX25vX2luZmx1ZW50aWFsLCBpbnRlcnZhbCA9ICJjb25maWRlbmNlIikKcHJlZF9wcmVkX2NsZWFuIDwtIHByZWRpY3QobG1fbW9kZWxfY2xlYW4sIG5ld2RhdGEgPSBkYXRhX25vX2luZmx1ZW50aWFsLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIikKCiMgQ29tYmluZSBldmVyeXRoaW5nIGludG8gYSBkYXRhIGZyYW1lCnBsb3RfZGF0YV9jbGVhbiA8LSBkYXRhX25vX2luZmx1ZW50aWFsICU+JQogIG11dGF0ZSgKICAgIHByZWRpY3RlZF9zdHJlYW1zID0gcHJlZF9jb25mX2NsZWFuWywgImZpdCJdLAogICAgY29uZl9sb3cgPSBwcmVkX2NvbmZfY2xlYW5bLCAibHdyIl0sCiAgICBjb25mX2hpZ2ggPSBwcmVkX2NvbmZfY2xlYW5bLCAidXByIl0sCiAgICBwcmVkX2xvdyA9IHByZWRfcHJlZF9jbGVhblssICJsd3IiXSwKICAgIHByZWRfaGlnaCA9IHByZWRfcHJlZF9jbGVhblssICJ1cHIiXQogICkgJT4lCiAgYXJyYW5nZShzdHJlYW1zKSAgIyBzb3J0IGJ5IGFjdHVhbCBzdHJlYW1zIGZvciBzbW9vdGggcmliYm9ucwpgYGAKCmBgYHtyfQojIFBsb3Qgd2l0aCBjb25maWRlbmNlIGFuZCBwcmVkaWN0aW9uIGludGVydmFscyBhcyBsaW5lcyAobm8gc2hhZGVkIHJpYmJvbnMpCmdncGxvdChwbG90X2RhdGFfY2xlYW4sIGFlcyh4ID0gcHJlZGljdGVkX3N0cmVhbXMsIHkgPSBzdHJlYW1zKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYsIGNvbG9yID0gImRhcmtibHVlIikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgbGluZXR5cGUgPSAic29saWQiLCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fbGluZShhZXMoeSA9IGNvbmZfbG93KSwgY29sb3IgPSAicHVycGxlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGdlb21fbGluZShhZXMoeSA9IGNvbmZfaGlnaCksIGNvbG9yID0gInB1cnBsZSIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkX2xvdyksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkX2hpZ2gpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkFjdHVhbCB2cyBQcmVkaWN0ZWQgU3RyZWFtcyB3aXRoIENvbmZpZGVuY2UgYW5kIFByZWRpY3Rpb24gSW50ZXJ2YWwgTGluZXMiLAogICAgeCA9ICJQcmVkaWN0ZWQgU3RyZWFtcyIsCiAgICB5ID0gIkFjdHVhbCBTdHJlYW1zIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGZtc2IpCmBgYAoKCmBgYHtyfQoKIyBEZWZpbmUgVUkKdWkgPC0gbmF2YmFyUGFnZSgKICB0aXRsZSA9IGRpdigKICAgICNpbWcoc3JjID0gInd3dy9zcG90aWZ5X2xvZ29fYmxhY2sucG5nIiwgaGVpZ2h0ID0gIjMwcHgiLCBzdHlsZSA9ICJtYXJnaW4tdG9wOi01cHg7IiksCiAgICAiU3BvdGlmeSBEYXRhIERhc2hib2FyZCIKICApLAogIGlkID0gIm1haW5fbmF2YmFyIiwKICAgICAgICAgICAgICAgICAKICAjIEN1c3RvbSBzdHlsaW5nCiAgaGVhZGVyID0gdGFncyRoZWFkKAogICAgdGFncyRzdHlsZShIVE1MKCIKICAgICAgLyogTmF2YmFyIGJhY2tncm91bmQgKi8KICAgICAgLm5hdmJhci1kZWZhdWx0IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMTkxNDE0OwogICAgICAgIGJvcmRlci1jb2xvcjogIzE5MTQxNDsKICAgICAgfQoKICAgICAgLyogTmF2YmFyIHRpdGxlIGFuZCB0YWIgdGV4dCAqLwogICAgICAubmF2YmFyLWRlZmF1bHQgLm5hdmJhci1icmFuZCwKICAgICAgLm5hdmJhci1kZWZhdWx0IC5uYXZiYXItbmF2ID4gbGkgPiBhIHsKICAgICAgICBjb2xvcjogd2hpdGUgIWltcG9ydGFudDsKICAgICAgICBmb250LXdlaWdodDogYm9sZDsKICAgICAgfQoKICAgICAgLyogQWN0aXZlIHRhYiBoaWdobGlnaHQgKi8KICAgICAgLm5hdmJhci1kZWZhdWx0IC5uYXZiYXItbmF2ID4gLmFjdGl2ZSA+IGEsCiAgICAgIC5uYXZiYXItZGVmYXVsdCAubmF2YmFyLW5hdiA+IC5hY3RpdmUgPiBhOmZvY3VzLAogICAgICAubmF2YmFyLWRlZmF1bHQgLm5hdmJhci1uYXYgPiAuYWN0aXZlID4gYTpob3ZlciB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzFEQjk1NCAhaW1wb3J0YW50OwogICAgICAgIGNvbG9yOiBibGFjayAhaW1wb3J0YW50OwogICAgICB9CgogICAgICAvKiBUYWIgY29udGVudCBiYWNrZ3JvdW5kICovCiAgICAgIC50YWItcGFuZSB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogIzEyMTIxMjsKICAgICAgICBjb2xvcjogd2hpdGU7CiAgICAgICAgcGFkZGluZzogMzBweDsKICAgICAgfQoKICAgICAgLyogQm9keSBiYWNrZ3JvdW5kICovCiAgICAgIGJvZHkgewogICAgICAgIGJhY2tncm91bmQtY29sb3I6ICMxMjEyMTI7CiAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICB9CgogICAgICAvKiBMaXN0IHN0eWxpbmcgZm9yIGJldHRlciB2aXNpYmlsaXR5ICovCiAgICAgIHVsIHsKICAgICAgICBjb2xvcjogd2hpdGU7CiAgICAgIH0KICAgICIpKQogICksCgojIC0tLSBIT01FIFRBQiAtLS0KICB0YWJQYW5lbCgiSG9tZSIsCiAgICBmbHVpZFBhZ2UoCiAgICAgIHRhZ3MkZGl2KAogICAgICAgIHN0eWxlID0gInRleHQtYWxpZ246IGNlbnRlcjsiLAogICAgICAgICNpbWcoc3JjID0gInd3dy9zcG90aWZ5X2xvZ29fZ3JlZW4ucG5nIiwgaGVpZ2h0ID0gIjEyMHB4IiksCiAgICAgICAgaDIoIldlbGNvbWUgdG8gdGhlIFNwb3RpZnkgRGF0YSBEYXNoYm9hcmQiKSwKICAgICAgICBwKCJUaGlzIGRhdGEgaW5jbHVkZXMgc29uZ3MgcmVsZWFzZWQgZnJvbSAxOTMwIC0gMjAyMy4gVXNlIHRoZSB0YWJzIGFib3ZlIHRvIGV4cGxvcmUgZGF0YSB2aXN1YWxpemF0aW9ucyBhbmQgbW9kZWxzIHJlbGF0ZWQgdG8gU3BvdGlmeSBzb25ncy4iKSwKICAgICAgICB0YWdzJHVsKAogICAgICAgICAgdGFncyRsaSgiRXhwbG9yZSB0cmVuZHMgYWNyb3NzIHJlbGVhc2UgeWVhcnMiKSwKICAgICAgICAgIHRhZ3MkbGkoIkRpc2NvdmVyIHRvcCBzdHJlYW1lZCB0cmFja3MiKSwKICAgICAgICAgIHRhZ3MkbGkoIlZpc3VhbGl6ZSBhdWRpbyBmZWF0dXJlcyB3aXRoIHJhZGFyIGFuZCBkZW5zaXR5IHBsb3RzIiksCiAgICAgICAgICB0YWdzJGxpKCJDb21wYXJlIHBsYXlsaXN0cyBhbmQgc3RyZWFtIHByZWRpY3Rpb25zIikKICAgICAgICApLAogICAgICAgIHAoIlVzZSB0aGUgdGFicyBhYm92ZSB0byBleHBsb3JlIHRoZSBmZWF0dXJlcy4iKQogICAgICApCiAgICApCiAgKSwgIAogIAogICMgR3JvdXA6IFJlbGVhc2UgWWVhciBQZXJmb3JtYW5jZQogIHRhYlBhbmVsKCJSZWxlYXNlIFllYXIgUGVyZm9ybWFuY2UiLAogICAgdGFic2V0UGFuZWwoCiAgICAgIHRhYlBhbmVsKCJEZW5zaXR5OiBSZWxlYXNlIFllYXIiLAogICAgICAgICAgICAgICBwbG90T3V0cHV0KCJkZW5zaXR5UGxvdCIpCiAgICAgICksCiAgICAgIHRhYlBhbmVsKCJUb3AgMTAgU3RyZWFtZWQgU29uZ3MgYnkgWWVhciIsCiAgICAgICAgICAgICAgIHBsb3RPdXRwdXQoInRvcDEwQmFyIikKICAgICAgKSwKICAgICAgdGFiUGFuZWwoIlRvcCA1IFNvbmdzIGJ5IFllYXIiLAogICAgICAgIHNpZGViYXJMYXlvdXQoCiAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgIHNlbGVjdElucHV0KCJ5ZWFyIiwgIlNlbGVjdCBZZWFyOiIsIGNob2ljZXMgPSBzb3J0KHVuaXF1ZSh5ZWFybHlfdG9wX3NvbmdzJHJlbGVhc2VkX3llYXIpLCBkZWNyZWFzaW5nID0gVFJVRSkpCiAgICAgICAgICApLAogICAgICAgICAgbWFpblBhbmVsKAogICAgICAgICAgICBwbG90T3V0cHV0KCJ0b3BTb25nc1Bsb3QiKQogICAgICAgICAgKQogICAgICAgICkKICAgICAgKQogICAgKQogICksCgogICAjIEdyb3VwOiBBdWRpbyBGZWF0dXJlcwogIHRhYlBhbmVsKCJSYWRhcjogVG9wIDMgU29uZ3MgQXVkaW8gRmVhdHVyZXMiLAogICAgc2lkZWJhckxheW91dCgKICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgIHNlbGVjdElucHV0KCJzZWxlY3RlZF95ZWFyIiwgIkNob29zZSBhIFllYXI6IiwKICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gc29ydCh1bmlxdWUoc3BvdGlmeSRyZWxlYXNlZF95ZWFyKSwgZGVjcmVhc2luZyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gbWF4KHNwb3RpZnkkcmVsZWFzZWRfeWVhciwgbmEucm0gPSBUUlVFKSkKICAgICAgKSwKICAgICAgbWFpblBhbmVsKAogICAgICAgIHBsb3RPdXRwdXQoInJhZGFyUGxvdCIpCiAgICAgICkKICAgICkKICApLAogIAogICMgR3JvdXA6IE1vZGUgJiBQbGF5bGlzdCBJbnNpZ2h0cwogIHRhYlBhbmVsKCJTY2F0dGVyOiBTdHJlYW1zIHZzIFBsYXlsaXN0cyBieSBNb2RlIiwKICAgIHNpZGViYXJMYXlvdXQoCiAgICAgIHNpZGViYXJQYW5lbCgKICAgICAgICBjaGVja2JveEdyb3VwSW5wdXQoIm1vZGVfc2VsZWN0IiwgIlNlbGVjdCBNb2RlKHMpOiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNob2ljZXMgPSB1bmlxdWUoc3BvdGlmeSRtb2RlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0ZWQgPSB1bmlxdWUoc3BvdGlmeSRtb2RlKSkKICAgICAgKSwKICAgICAgbWFpblBhbmVsKAogICAgICAgIHBsb3RPdXRwdXQoInNjYXR0ZXJQbG90IikKICAgICAgKQogICAgKQogICksCgoKICAjIEdyb3VwOiBQbGF5bGlzdCBNZXRyaWNzICYgUHJlZGljdGlvbnMKICB0YWJQYW5lbCgiUGxheWxpc3RzICYgU3RyZWFtcyBSZWxhdGlvbnNoaXAiLAogICAgdGFic2V0UGFuZWwoCiAgICAgIHRhYlBhbmVsKCJQYWlycyBQbG90IiwKICAgICAgICAgICAgICAgcGxvdE91dHB1dCgicGFpcnNQbG90IikKICAgICAgKSwKICAgICAgdGFiUGFuZWwoIk1vZGVsIFByZWRpY3Rpb25zIiwKICAgICAgICBmbHVpZFJvdygKICAgICAgICAgIGNvbHVtbig2LAogICAgICAgICAgICBoNCgiUHJlZGljdCBTdHJlYW1zIiksCiAgICAgICAgICAgIG51bWVyaWNJbnB1dCgic3BvdGlmeSIsICJTcG90aWZ5IFBsYXlsaXN0czoiLCB2YWx1ZSA9IDUwMDAsIG1pbiA9IDApLAogICAgICAgICAgICBudW1lcmljSW5wdXQoImRlZXplciIsICJEZWV6ZXIgUGxheWxpc3RzOiIsIHZhbHVlID0gMTAwMCwgbWluID0gMCksCiAgICAgICAgICAgIG51bWVyaWNJbnB1dCgiYXBwbGUiLCAiQXBwbGUgUGxheWxpc3RzOiIsIHZhbHVlID0gMjAwMCwgbWluID0gMCksCiAgICAgICAgICAgIGFjdGlvbkJ1dHRvbigicHJlZGljdCIsICJQcmVkaWN0IFN0cmVhbXMiKSwKICAgICAgICAgICAgYnIoKSwgYnIoKSwKICAgICAgICAgICAgaDUoIlByZWRpY3RlZCBTdHJlYW1zOiIpLAogICAgICAgICAgICB2ZXJiYXRpbVRleHRPdXRwdXQoInByZWRpY3Rpb24iKQogICAgICAgICAgKSwKICAgICAgICAgIGNvbHVtbig2LAogICAgICAgICAgICBoNCgiQWN0dWFsIHZzIFByZWRpY3RlZCBTdHJlYW1zIiksCiAgICAgICAgICAgIHBsb3RPdXRwdXQoInByZWRpY3Rpb25QbG90IikKICAgICAgICAgICkKICAgICAgICApCiAgICAgICkKICAgICkKICApCikKCgoKIyBEZWZpbmUgU2VydmVyCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgCiAgIyBEZW5zaXR5IFBsb3QgLS0tLQogIG91dHB1dCRkZW5zaXR5UGxvdCA8LSByZW5kZXJQbG90KHsKICAgIHBsb3QoZGVuc2l0eShzcG90aWZ5JHJlbGVhc2VkX3llYXIsIG5hLnJtID0gVFJVRSksCiAgICAgICAgIG1haW4gPSAiRGVuc2l0eSBQbG90IG9mIFJlbGVhc2VkIFllYXIiLAogICAgICAgICB4bGFiID0gIlJlbGVhc2VkIFllYXIiLCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IDIpCiAgfSkKICAKICAjIFNjYXR0ZXIgUGxvdCAtLS0tCiAgb3V0cHV0JHNjYXR0ZXJQbG90IDwtIHJlbmRlclBsb3QoewogICAgZmlsdGVyZWRfZGF0YSA8LSBzcG90aWZ5W3Nwb3RpZnkkbW9kZSAlaW4lIGlucHV0JG1vZGVfc2VsZWN0LCBdCiAgICAKICAgIGdncGxvdChmaWx0ZXJlZF9kYXRhLCBhZXMoeCA9IHN0cmVhbXMsIHkgPSBpbl9zcG90aWZ5X3BsYXlsaXN0cywgY29sb3IgPSBtb2RlKSkgKwogICAgICBnZW9tX3BvaW50KCkgKwogICAgICBsYWJzKHRpdGxlID0gIlN0cmVhbXMgdnMgUGxheWxpc3QgTWV0cmljcyBieSBNb2RlIiwKICAgICAgICAgICB4ID0gIlN0cmVhbXMiLCB5ID0gIk51bWJlciBpbiBTcG90aWZ5IFBsYXlsaXN0cyIpICsKICAgICAgdGhlbWVfbWluaW1hbCgpCiAgfSkKICAKICAjIFRvcCAxMCBTdHJlYW1lZCBTb25ncyBieSBZZWFyIC0tLS0KICBvdXRwdXQkdG9wMTBCYXIgPC0gcmVuZGVyUGxvdCh7CiAgICBnZ3Bsb3QodG9wMTBfeWVhcmx5LCBhZXMoeCA9IGZhY3RvcihyZWxlYXNlZF95ZWFyKSwgeSA9IHN0cmVhbXMsIGZpbGwgPSBzb25nLmFydGlzdCkpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICAgICAgbGFicyh0aXRsZSA9ICJUb3AgMTAgU3RyZWFtZWQgU29uZ3MgQnkgWWVhciIsCiAgICAgICAgICAgeCA9ICJZZWFyIiwgeSA9ICJTdHJlYW1zIiwgZmlsbCA9ICJTb25nIC0gQXJ0aXN0IikgKwogICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQogIH0pCiAgCiAgIyBUb3AgNSBTb25ncyBwZXIgWWVhciAtLS0tCiAgb3V0cHV0JHRvcFNvbmdzUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIHNlbGVjdGVkX3llYXJfZGF0YSA8LSB5ZWFybHlfdG9wX3NvbmdzICU+JQogICAgICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSBpbnB1dCR5ZWFyKQogICAgCiAgICBzZWxlY3RlZF95ZWFyX2RhdGEkdHJhY2tfbmFtZSA8LSBpY29udihzZWxlY3RlZF95ZWFyX2RhdGEkdHJhY2tfbmFtZSwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIlVURi04Iiwgc3ViID0gIioiKQogICAgCiAgICBnZ3Bsb3Qoc2VsZWN0ZWRfeWVhcl9kYXRhLCBhZXMoeCA9IHJlb3JkZXIodHJhY2tfbmFtZSwgLXN0cmVhbXMpLCB5ID0gc3RyZWFtcywgZmlsbCA9IHNvbmcuYXJ0aXN0KSkgKwogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIlRvcCA1IFN0cmVhbWVkIFNvbmdzIGluIiwgaW5wdXQkeWVhciksCiAgICAgICAgICAgeCA9ICJTb25nIiwgeSA9ICJTdHJlYW1zIiwgZmlsbCA9ICJTb25nIC0gQXJ0aXN0IikgKwogICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQogIH0pCiAgCiAgIyBSYWRhciBDaGFydCAtLS0tCiAgcmFkYXJfbWF0cml4MyA8LSByZWFjdGl2ZSh7CiAgICByZXEoaW5wdXQkc2VsZWN0ZWRfeWVhcikKICAgIHRvcDNfeWVhciA8LSBzcG90aWZ5ICU+JQogICAgICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSBpbnB1dCRzZWxlY3RlZF95ZWFyKSAlPiUKICAgICAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc3RyZWFtcywgbiA9IDMsIHdpdGhfdGllcyA9IEZBTFNFKSAlPiUKICAgICAgZHBseXI6OnJlbmFtZSgKICAgICAgICBkYW5jZWFiaWxpdHkgPSBgZGFuY2VhYmlsaXR5Xy5gLAogICAgICAgIHNwZWVjaGluZXNzID0gYHNwZWVjaGluZXNzXy5gLAogICAgICAgIGVuZXJneSA9IGBlbmVyZ3lfLmAsCiAgICAgICAgYWNvdXN0aWNuZXNzID0gYGFjb3VzdGljbmVzc18uYAogICAgICApICU+JQogICAgICBkcGx5cjo6c2VsZWN0KHNvbmcuYXJ0aXN0LCBicG0sIGRhbmNlYWJpbGl0eSwgc3BlZWNoaW5lc3MsIGVuZXJneSwgYWNvdXN0aWNuZXNzKQogICAgCiAgICByYWRhcl9kYXRhX25vcm0zIDwtIHRvcDNfeWVhciAlPiUKICAgICAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiByZXNjYWxlKC54LCB0byA9IGMoMCwgMSkpKSkKICAgIAogICAgaWYgKG5yb3cocmFkYXJfZGF0YV9ub3JtMykgPCAxKSByZXR1cm4oTlVMTCkKICAgIAogICAgbWF4X21pbjMgPC0gZGF0YS5mcmFtZSgKICAgICAgYnBtID0gMSwgZGFuY2VhYmlsaXR5ID0gMSwgc3BlZWNoaW5lc3MgPSAxLCBlbmVyZ3kgPSAxLCBhY291c3RpY25lc3MgPSAxLAogICAgICByb3cubmFtZXMgPSBjKCJNYXgiKQogICAgKSAlPiUKICAgICAgYmluZF9yb3dzKGRhdGEuZnJhbWUoCiAgICAgICAgYnBtID0gMCwgZGFuY2VhYmlsaXR5ID0gMCwgc3BlZWNoaW5lc3MgPSAwLCBlbmVyZ3kgPSAwLCBhY291c3RpY25lc3MgPSAwLAogICAgICAgIHJvdy5uYW1lcyA9IGMoIk1pbiIpCiAgICAgICkpCiAgICAKICAgIGJpbmRfcm93cyhtYXhfbWluMywgcmFkYXJfZGF0YV9ub3JtMyAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJzb25nLmFydGlzdCIpKQogIH0pCiAgCiAgb3V0cHV0JHJhZGFyUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIG1hdHJpeCA8LSByYWRhcl9tYXRyaXgzKCkKICAgIHJlcShtYXRyaXgpCiAgICAKICAgIGNvbG9yc19ib3JkZXIgPC0gcmFpbmJvdyhucm93KG1hdHJpeCkgLSAyKQogICAgY29sb3JzX2luIDwtIGFkanVzdGNvbG9yKGNvbG9yc19ib3JkZXIsIGFscGhhLmYgPSAwLjI1KQogICAgCiAgICBsYXlvdXQobWF0cml4KGMoMSwgMiksIG5yb3cgPSAyKSwgaGVpZ2h0cyA9IGMoNCwgMSkpCiAgICBwYXIobWFyID0gYygyLCAyLCA0LCAyKSkKICAgIHJhZGFyY2hhcnQoCiAgICAgIG1hdHJpeCwKICAgICAgYXhpc3R5cGUgPSAxLAogICAgICBwY29sID0gY29sb3JzX2JvcmRlciwKICAgICAgcGZjb2wgPSBjb2xvcnNfaW4sCiAgICAgIHBsd2QgPSAyLAogICAgICBwbHR5ID0gMSwKICAgICAgY2dsY29sID0gImdyZXkiLAogICAgICBjZ2x0eSA9IDEsCiAgICAgIGF4aXNsYWJjb2wgPSAiZ3JleSIsCiAgICAgIGNheGlzbGFiZWxzID0gc2VxKDAsIDEsIDAuMiksCiAgICAgIGNnbHdkID0gMC44LAogICAgICB2bGNleCA9IDAuOSwKICAgICAgdGl0bGUgPSBwYXN0ZSgiVG9wIDMgU29uZ3MgaW4iLCBpbnB1dCRzZWxlY3RlZF95ZWFyKQogICAgKQogICAgCiAgICBwYXIobWFyID0gYygwLCAwLCAwLCAwKSkKICAgIHBsb3QubmV3KCkKICAgIGxlZ2VuZCgiY2VudGVyIiwgbGVnZW5kID0gcm93bmFtZXMobWF0cml4KVstYygxLCAyKV0sCiAgICAgICAgICAgYnR5ID0gIm4iLCBwY2ggPSAyMCwgY29sID0gY29sb3JzX2JvcmRlciwgdGV4dC5jb2wgPSAiYmxhY2siLCBjZXggPSAwLjkpCiAgfSkKICAKICAjIFBhaXJzIFBsb3QgLS0tLQogIG91dHB1dCRwYWlyc1Bsb3QgPC0gcmVuZGVyUGxvdCh7CiAgICBzZWxlY3RlZF9kYXRhIDwtIGRwbHlyOjpzZWxlY3Qoc3BvdGlmeSwgaW5fc3BvdGlmeV9wbGF5bGlzdHMsIGluX2RlZXplcl9wbGF5bGlzdHMsIGluX2FwcGxlX3BsYXlsaXN0cywgc3RyZWFtcykKICAgIHBhaXJzKHNlbGVjdGVkX2RhdGEsIG1haW4gPSAiUGFpcnMgUGxvdCBvZiBQbGF5bGlzdCBDb3VudHMgYW5kIFN0cmVhbXMiKQogIH0pCiAgCiAgIyBBY3R1YWwgdnMgUHJlZGljdGVkIFBsb3QgLS0tLQogIG91dHB1dCRwcmVkaWN0aW9uUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gcHJlZGljdGVkX3N0cmVhbXMsIHkgPSBzdHJlYW1zKSkgKwogICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICAgICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG9yID0gImJsdWUiKSArCiAgICAgIGdlb21fbGluZShhZXMoeSA9IGNvbmZfbG93KSwgY29sb3IgPSAicHVycGxlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogICAgICBnZW9tX2xpbmUoYWVzKHkgPSBjb25mX2hpZ2gpLCBjb2xvciA9ICJwdXJwbGUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgICAgIGdlb21fbGluZShhZXMoeSA9IHByZWRfbG93KSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogICAgICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkX2hpZ2gpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgICAgIGxhYnModGl0bGUgPSAiQWN0dWFsIHZzIFByZWRpY3RlZCBTdHJlYW1zIChSXjIgPSAwLjY4KSIsCiAgICAgICAgICAgeCA9ICJQcmVkaWN0ZWQgU3RyZWFtcyIsIHkgPSAiQWN0dWFsIFN0cmVhbXMiKSArCiAgICAgIHRoZW1lX21pbmltYWwoKQogIH0pCiAgCiAgIyBQcmVkaWN0IFN0cmVhbXMgLS0tLQogIG9ic2VydmVFdmVudChpbnB1dCRwcmVkaWN0LCB7CiAgICBuZXdfaW5wdXQgPC0gZGF0YS5mcmFtZSgKICAgICAgaW5fc3BvdGlmeV9wbGF5bGlzdHMgPSBpbnB1dCRzcG90aWZ5LAogICAgICBpbl9kZWV6ZXJfcGxheWxpc3RzID0gaW5wdXQkZGVlemVyLAogICAgICBpbl9hcHBsZV9wbGF5bGlzdHMgPSBpbnB1dCRhcHBsZQogICAgKQogICAgCiAgICBwcmVkaWN0ZWQgPC0gcHJlZGljdChjdl9tb2RlbCwgbmV3ZGF0YSA9IG5ld19pbnB1dCkKICAgIAogICAgb3V0cHV0JHByZWRpY3Rpb24gPC0gcmVuZGVyVGV4dCh7CiAgICAgIGZvcm1hdChyb3VuZChwcmVkaWN0ZWQsIDApLCBiaWcubWFyayA9ICIsIikKICAgIH0pCiAgfSkKfQoKIyBSdW4gdGhlIEFwcApzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCgpgYGAKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICBpbWcoc3JjID0gInNwb3RpZnlfbG9nb19ibGFjay5wbmciLCBoZWlnaHQgPSAiMTAwcHgiKQopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkge30KCnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYApgYGB7cn0KZmlsZS5leGlzdHMoInd3dzpzcG90aWZ5X2xvZ29fYmxhY2sucG5nIikKYGBgCmBgYHtyfQpsaXN0LmZpbGVzKCJ3d3ciKQpgYGAKYGBge3IsIGVjaG89RkFMU0V9CnRhZ3MkaW1nKHNyYyA9ICJ3d3cvc3BvdGlmeV9sb2dvX2JsYWNrLnBuZyIsIGhlaWdodCA9ICI4MHB4IikKYGBgCmBgYHtyfQpnZXR3ZCgpCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQp0YWdzJGltZyhzcmMgPSAid3d3L3Nwb3RpZnlfbG9nb19ibGFjay5wbmciLCBoZWlnaHQgPSAiODBweCIpCmBgYAoKYGBge3J9Cmxpc3QuZmlsZXMoInd3dy8iKQpgYGAKYGBge3J9CnRpdGxlOiAiU3BvdGlmeSBEYXNoYm9hcmQiCm91dHB1dDogCiAgZmxleGRhc2hib2FyZDo6ZmxleF9kYXNoYm9hcmQ6CiAgICBvcmllbnRhdGlvbjogY29sdW1ucwogICAgcnVudGltZTogc2hpbnkKCmltZyhzcmMgPSAic3BvdGlmeV9sb2dvX2JsYWNrLnBuZyIsIGhlaWdodCA9ICIzMHB4IikKYGBgCgpgYGB7cn0KWUFNTAotLS0KdGl0bGU6ICJTcG90aWZ5IERhdGEgRGFzaGJvYXJkIgpvdXRwdXQ6IGh0bWxfZG9jdW1lbnQKcnVudGltZTogc2hpbnkKLS0tCnNoaW55OjphZGRSZXNvdXJjZVBhdGgoInd3dyIsICJ3d3ciKQoKIyBEZWZpbmUgVUkKdWkgPC0gbmF2YmFyUGFnZSgKICB0aXRsZSA9IGRpdigKICAgIGltZyhzcmMgPSAid3d3L3Nwb3RpZnlfbG9nb19ncmVlbi5wbmciLCBoZWlnaHQgPSAiMzBweCIsIHN0eWxlID0gIm1hcmdpbi10b3A6LTVweDsiKSwKICAgICJTcG90aWZ5IERhdGEgRGFzaGJvYXJkIgogICksCiAgaWQgPSAibWFpbl9uYXZiYXIiLAogICAgICAgICAgICAgICAgIAogICMgQ3VzdG9tIHN0eWxpbmcKICBoZWFkZXIgPSB0YWdzJGhlYWQoCiAgdGFncyRzdHlsZShIVE1MKCIKICAgIC8qIE5hdmJhciBiYWNrZ3JvdW5kICovCiAgICAubmF2YmFyLWRlZmF1bHQgewogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjMTkxNDE0OwogICAgICBib3JkZXItY29sb3I6ICMxOTE0MTQ7CiAgICB9CgogICAgLyogTmF2YmFyIHRpdGxlIChicmFuZCkgdGV4dCBjb2xvciAqLwogICAgLm5hdmJhci1kZWZhdWx0IC5uYXZiYXItYnJhbmQgewogICAgICBjb2xvcjogIzFEQjk1NCAhaW1wb3J0YW50OyAvKiBTcG90aWZ5IGdyZWVuICovCiAgICB9CgogICAgLyogTmF2YmFyIHRpdGxlIGhvdmVyL2ZvY3VzICovCiAgICAubmF2YmFyLWRlZmF1bHQgLm5hdmJhci1icmFuZDpob3ZlciwKICAgIC5uYXZiYXItZGVmYXVsdCAubmF2YmFyLWJyYW5kOmZvY3VzIHsKICAgICAgY29sb3I6ICMxZWQ3NjAgIWltcG9ydGFudDsKICAgIH0KCiAgICAvKiBUYWIgcGFuZWwgKG1lbnUgaXRlbXMpIHRleHQgY29sb3IgKi8KICAgIC5uYXZiYXItZGVmYXVsdCAubmF2YmFyLW5hdiA+IGxpID4gYSB7CiAgICAgIGNvbG9yOiAjMURCOTU0ICFpbXBvcnRhbnQ7CiAgICB9CgogICAgLyogVGFiIGhvdmVyL2ZvY3VzICovCiAgICAubmF2YmFyLWRlZmF1bHQgLm5hdmJhci1uYXYgPiBsaSA+IGE6aG92ZXIsCiAgICAubmF2YmFyLWRlZmF1bHQgLm5hdmJhci1uYXYgPiBsaSA+IGE6Zm9jdXMgewogICAgICBjb2xvcjogIzFlZDc2MCAhaW1wb3J0YW50OwogICAgICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsKICAgIH0KCiAgICAvKiBLZWVwIG1haW4gcGFuZWwgYmFja2dyb3VuZCAqLwogICAgLm1haW4tcGFuZWwsIC5jb2wtc20tOCB7CiAgICAgIGJhY2tncm91bmQtY29sb3I6ICNmNWY1ZjUgIWltcG9ydGFudDsKICAgICAgY29sb3I6IGJsYWNrICFpbXBvcnRhbnQ7CiAgICAgIHBhZGRpbmc6IDIwcHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDZweDsKICAgIH0KICAiKSkKKSwKCiMgLS0tIEhPTUUgVEFCIC0tLQogIHRhYlBhbmVsKCJIb21lIiwKICAgIGZsdWlkUGFnZSgKICAgICAgdGFncyRkaXYoCiAgICAgICAgc3R5bGUgPSAidGV4dC1hbGlnbjogbGVmdDsiLAogICAgICAgIGltZyhzcmMgPSAid3d3L3Nwb3RpZnlfbG9nb19ibGFjay5wbmciLCBoZWlnaHQgPSAiMTIwcHgiKSwKICAgICAgICBoMigiV2VsY29tZSB0byB0aGUgU3BvdGlmeSBEYXRhIERhc2hib2FyZCIpLAogICAgICAgIHAoIlRoaXMgZGF0YSBpbmNsdWRlcyBzb25ncyByZWxlYXNlZCBmcm9tIDE5MzAgLSAyMDIzLiBVc2UgdGhlIHRhYnMgYWJvdmUgdG8gZXhwbG9yZSBkYXRhIHZpc3VhbGl6YXRpb25zIGFuZCBtb2RlbHMgcmVsYXRlZCB0byBTcG90aWZ5IHNvbmdzLiIpLAogICAgICAgIHRhZ3MkdWwoCiAgICAgICAgICB0YWdzJGxpKCJFeHBsb3JlIHRyZW5kcyBhY3Jvc3MgcmVsZWFzZSB5ZWFycyIpLAogICAgICAgICAgdGFncyRsaSgiRGlzY292ZXIgdG9wIHN0cmVhbWVkIHRyYWNrcyIpLAogICAgICAgICAgdGFncyRsaSgiVmlzdWFsaXplIGF1ZGlvIGZlYXR1cmVzIHdpdGggcmFkYXIgYW5kIHNjYXR0ZXIgcGxvdHMiKSwKICAgICAgICAgIHRhZ3MkbGkoIkNvbXBhcmUgcGxheWxpc3RzIGFuZCBzdHJlYW0gcHJlZGljdGlvbnMiKQogICAgICAgICksCiAgICAgICAgcCgiVXNlIHRoZSB0YWJzIGFib3ZlIHRvIGV4cGxvcmUgdGhlIGZlYXR1cmVzLiIpCiAgICAgICkKICAgICkKICApLCAgCiAgCgogICMgR3JvdXA6IFJlbGVhc2UgWWVhciBQZXJmb3JtYW5jZQogIHRhYlBhbmVsKCJSZWxlYXNlIFllYXIgUGVyZm9ybWFuY2UiLAogICAgdGFic2V0UGFuZWwoCiAgICAgIHRhYlBhbmVsKCJEZW5zaXR5OiBSZWxlYXNlIFllYXIiLAogICAgICAgICAgICAgICBwbG90T3V0cHV0KCJkZW5zaXR5UGxvdCIpCiAgICAgICksCiAgICAgIHRhYlBhbmVsKCJUb3AgMTAgT3ZlcmFsbCAtIFRvcCBTdHJlYW1lZCBTb25nIEZyb20gRWFjaCBSZWxlYXNlZCBZZWFyIiwKICAgICAgICAgICAgICAgcGxvdE91dHB1dCgidG9wMTBCYXIiKSwKICAgICAgICAgICAgICAgcCgnRmlsdGVyaW5nIGZvciB0aGUgdG9wIHN0cmVhbWVkIGZyb20gZWFjaCByZWxlYXNlZCB5ZWFyIGFuZCB0aGVuIGRpc3BsYXlpbmcgd2hpY2ggc29uZ3MgcmFua2VkIGluIHRvcCAxMCBieSBvdmVyYWxsIHN0cmVhbSBjb3VudHMuJykKICAgICAgKSwKICAgICAgdGFiUGFuZWwoIlRvcCA1IFNvbmdzIGJ5IFllYXIiLAogICAgICAgIHNpZGViYXJMYXlvdXQoCiAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgIHNlbGVjdElucHV0KCJ5ZWFyIiwgIlNlbGVjdCBZZWFyOiIsIGNob2ljZXMgPSBzb3J0KHVuaXF1ZSh5ZWFybHlfdG9wX3NvbmdzJHJlbGVhc2VkX3llYXIpLCBkZWNyZWFzaW5nID0gVFJVRSkpCiAgICAgICAgICApLAogICAgICAgICAgbWFpblBhbmVsKAogICAgICAgICAgICBwbG90T3V0cHV0KCJ0b3BTb25nc1Bsb3QiKQogICAgICAgICAgKQogICAgICAgICkKICAgICAgKQogICAgKQogICksCgogICAjIEdyb3VwOiBBdWRpbyBGZWF0dXJlcwogIHRhYlBhbmVsKCJSYWRhcjogVG9wIDMgU29uZ3MgQXVkaW8gRmVhdHVyZXMiLAogICAgc2lkZWJhckxheW91dCgKICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgIHNlbGVjdElucHV0KCJzZWxlY3RlZF95ZWFyIiwgIkNob29zZSBhIFllYXI6IiwKICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gc29ydCh1bmlxdWUoc3BvdGlmeSRyZWxlYXNlZF95ZWFyKSwgZGVjcmVhc2luZyA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gbWF4KHNwb3RpZnkkcmVsZWFzZWRfeWVhciwgbmEucm0gPSBUUlVFKSksCiAgICAgICAgdGFncyR1bCgKICAgICAgICAgIHN0eWxlID0gInBhZGRpbmctbGVmdDogMTVweDsgbWFyZ2luLWxlZnQ6IDA7IiwgIyBBZGp1c3QgcGFkZGluZyBhcyBuZWVkZWQKICAgICAgICAgIHRhZ3MkbGkoIkJQTSAtIEJlYXRzIHBlciBtaW51dGUsIHRlbXBvIG9yIHNwZWVkIG9mIHRoZSBzb25nLiAiKSwKICAgICAgICAgIHRhZ3MkbGkoIkFjb3VzdGljbmVzcyAtIENvbmZpZGVuY2UgbWVhc3VyZSBvZiBob3cgbXVjaCBhIHRyYWNrIHNvdW5kcyBsaWtlIGl0IHdhcyBtYWRlIHdpdGggbGl2ZSBpbnN0cnVtZW50cyBhbmQgbmF0dXJhbCBzb3VuZHMsIHJhdGhlciB0aGFuIGVsZWN0cm9uaWMgb3Igc3ludGhlc2l6ZWQgc291bmRzLiAiKSwKICAgICAgICAgIHRhZ3MkbGkoIkVuZXJneSAtIEVuY29tcGFzc2VzIGEgYnJvYWRlciByYW5nZSBvZiBlbGVtZW50cywgaW5jbHVkaW5nIG1vbWVudHVtLCBpbnRlbnNpdHksIGFuZCBlbW90aW9uYWwgaW1wYWN0LiIpLAogICAgICAgICAgdGFncyRsaSgiU3BlZWNoaW5lc3MgLSBNZWFzdXJlIG9mIGhvdyBtdWNoIHNwb2tlbiB3b3JkIGNvbnRlbnQgaXMgcHJlc2VudCBpbiBhIHRyYWNrLCBhcyBvcHBvc2VkIHRvIHB1cmVseSBtdXNpY2FsIGVsZW1lbnRzLiIpLAogICAgICAgICAgdGFncyRsaSgiRGFuY2VhYmlsaXR5IC0gTWVhc3VyZSBvZiBob3cgZWFzaWx5IHNvbWVvbmUgY291bGQgbW92ZSB0aGVpciBib2R5IHRvIHRoZSByaHl0aG0gYW5kIHN0cnVjdHVyZSBvZiB0aGUgbXVzaWMuIiksCiAgICAgICAgKQogICAgICApLAogICAgICBtYWluUGFuZWwoCiAgICAgICAgcGxvdE91dHB1dCgicmFkYXJQbG90IikKICAgICAgKQogICAgKQogICksCgoKICAjIEdyb3VwOiBQbGF5bGlzdCBNZXRyaWNzICYgUHJlZGljdGlvbnMKICB0YWJQYW5lbCgiUGxheWxpc3RzICYgU3RyZWFtcyBSZWxhdGlvbnNoaXAiLAogICAgdGFic2V0UGFuZWwoCiAgICB0YWJQYW5lbCgiU2NhdHRlcjogU3RyZWFtcyB2cyBQbGF5bGlzdHMgYnkgTW9kZSIsCiAgICBzaWRlYmFyTGF5b3V0KAogICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgY2hlY2tib3hHcm91cElucHV0KCJtb2RlX3NlbGVjdCIsICJTZWxlY3QgTW9kZShzKToiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gdW5pcXVlKHNwb3RpZnkkbW9kZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gdW5pcXVlKHNwb3RpZnkkbW9kZSkpLAogICAgICAgIHRhZ3MkdWwoCiAgICAgICAgICBzdHlsZSA9ICJwYWRkaW5nLWxlZnQ6IDE1cHg7IG1hcmdpbi1sZWZ0OiAwOyIsICMgQWRqdXN0IHBhZGRpbmcgYXMgbmVlZGVkCiAgICAgICAgICB0YWdzJGxpKCJNb2RlcyByZWZlciB0byB0d28gZGlmZmVyZW50IHR5cGVzIG9mIG11c2ljYWwgc2NhbGVzIGFuZCBrZXlzLiIpLAogICAgICAgICAgdGFncyRsaSgiTWFqb3IgLSBTb3VuZHMgYnJpZ2h0LCBoYXBweSwgYW5kIHVwbGlmdGluZy4iKSwKICAgICAgICAgIHRhZ3MkbGkoIk1pbm9yIC0gU291bmRzIHNhZCwgbWVsYW5jaG9saWMsIG9yIGRhcmsuIiksCiAgICAgICAgKQogICAgICApLAogICAgICAgCiAgICAgIG1haW5QYW5lbCgKICAgICAgICBwbG90T3V0cHV0KCJzY2F0dGVyUGxvdCIpLAogICAgICApCiAgICApCiAgKSwKICAgICAgCiAgICAgIAogICAgICB0YWJQYW5lbCgiUGFpcnMgUGxvdCIsCiAgICAgICAgICAgICAgIHBsb3RPdXRwdXQoInBhaXJzUGxvdCIpLAogICAgICAgICAgICAgICBwKCdUaGUgcGFpcnMgcGxvdCBpcyBzaG93aW5nIHRoZSBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG51bWJlciBvZiBzdHJlYW1zIHRvIHRoZSBudW1iZXIgb2YgcGxheWxpc3RzIHNvbmdzIGFwcGVhciBpbi4gU3BvdGlmeSwgRGVlemVyLCBhbmQgQXBwbGUgcGxhbHlsaXN0IGFwcGVhcmFuY2VzIGhhZCB0aGUgbW9zdCBsaW5lYXIgcmVsYXRpb25zaGlwIHRvIHN0cmVhbXMuJykKICAgICAgKSwKICAgICAgdGFiUGFuZWwoIk1vZGVsIFByZWRpY3Rpb25zIiwKICAgICAgICBmbHVpZFJvdygKICAgICAgICAgIGNvbHVtbig2LAogICAgICAgICAgICBwKCdJbnB1dCBhbiBlc3RpbWF0ZSBmb3IgdGhlIG51bWJlciBvZiBhcHBlYXJhbmNlcyBpbiBlYWNoIG9mIHRoZSBwbGF5bGlzdHMgaW4gb3JkZXIgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIHN0cmVhbXMuIFRoZSBtb2RlbCBpcyB0cmFpbmVkIG9uIHRoaXMgZGF0YSB0byBwcmVkaWN0IHRoZSBudW1iZXIgb2YgdG90YWwgc3RyZWFtcyAoMjAwOC0yMDI0KS4gCiAgICAgICAgICAgICAnKSwKICAgICAgICAgICAgaDQoIlByZWRpY3QgU3RyZWFtcyIpLAogICAgICAgICAgICBudW1lcmljSW5wdXQoInNwb3RpZnkiLCAiU3BvdGlmeSBQbGF5bGlzdHM6IiwgdmFsdWUgPSA1MDAwLCBtaW4gPSAwKSwKICAgICAgICAgICAgbnVtZXJpY0lucHV0KCJkZWV6ZXIiLCAiRGVlemVyIFBsYXlsaXN0czoiLCB2YWx1ZSA9IDEwMDAsIG1pbiA9IDApLAogICAgICAgICAgICBudW1lcmljSW5wdXQoImFwcGxlIiwgIkFwcGxlIFBsYXlsaXN0czoiLCB2YWx1ZSA9IDIwMDAsIG1pbiA9IDApLAogICAgICAgICAgICBhY3Rpb25CdXR0b24oInByZWRpY3QiLCAiUHJlZGljdCBTdHJlYW1zIiksCiAgICAgICAgICAgIGJyKCksIGJyKCksCiAgICAgICAgICAgIGg1KCJQcmVkaWN0ZWQgU3RyZWFtczoiKSwKICAgICAgICAgICAgdmVyYmF0aW1UZXh0T3V0cHV0KCJwcmVkaWN0aW9uIikKICAgICAgICAgICksCiAgICAgICAgICAKICAgICAgICAgIGNvbHVtbig2LAogICAgICAgICAgICBoNCgiQWN0dWFsIHZzIFByZWRpY3RlZCBTdHJlYW1zIiksCiAgICAgICAgICAgIHBsb3RPdXRwdXQoInByZWRpY3Rpb25QbG90IiksCiAgICAgICAgICAgICBwKCdUaGUgbGluZWFyIG1vZGVsIGRpc3BsYXllZCBpcyB0cmFpbmVkIG9uIHN0cmVhbXMgaW4gcmVsYXRpb24gdG8gU3BvdGlmeSwgRGVlemVyLCBhbmQgQXBwbGUgcGxheWxpc3QgYXBwZWFyYW5jZXMuIFVzaW5nIGRhdGEgZnJvbSBlYWNoIHNvbmcsIGl0IGlzIHBsb3R0aW5nIHRoZSBhY3R1YWwgc3RyZWFtIG51bWJlcnMgdG8gdGhlIHByZWRpY3RlZCB2YWx1ZSB3aXRoIGNvbmZpZGVuY2UgYW5kIHByZWRpY3RpdmUgaW50ZXJ2YWxzLicpCiAgICAgICAgICApCiAgICAgICAgKQogICAgICApCiAgICApCiAgKQopCgoKCiMgRGVmaW5lIFNlcnZlcgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIAogICMgRGVuc2l0eSBQbG90IC0tLS0KICBvdXRwdXQkZGVuc2l0eVBsb3QgPC0gcmVuZGVyUGxvdCh7CiAgICBwbG90KGRlbnNpdHkoc3BvdGlmeSRyZWxlYXNlZF95ZWFyLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICBtYWluID0gIkRlbnNpdHkgUGxvdCBvZiBTb25nIENvdW50cyBieSBSZWxlYXNlZCBZZWFyIiwKICAgICAgICAgeGxhYiA9ICJSZWxlYXNlZCBZZWFyIiwgY29sID0gImJsdWUiLCBsd2QgPSAyKQogIH0pCiAgCiAgIyBTY2F0dGVyIFBsb3QgLS0tLQogIG91dHB1dCRzY2F0dGVyUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGZpbHRlcmVkX2RhdGEgPC0gc3BvdGlmeVtzcG90aWZ5JG1vZGUgJWluJSBpbnB1dCRtb2RlX3NlbGVjdCwgXQogICAgCiAgICBnZ3Bsb3QoZmlsdGVyZWRfZGF0YSwgYWVzKHggPSBzdHJlYW1zLCB5ID0gaW5fc3BvdGlmeV9wbGF5bGlzdHMsIGNvbG9yID0gbW9kZSkpICsKICAgICAgZ2VvbV9wb2ludCgpICsKICAgICAgbGFicyh0aXRsZSA9ICJTdHJlYW1zIHZzIFBsYXlsaXN0IE1ldHJpY3MgYnkgTW9kZSIsCiAgICAgICAgICAgeCA9ICJTdHJlYW1zIiwgeSA9ICJOdW1iZXIgaW4gU3BvdGlmeSBQbGF5bGlzdHMiKSArCiAgICAgIHRoZW1lX21pbmltYWwoKQogIH0pCiAgCiAgIyBUb3AgMTAgU3RyZWFtZWQgU29uZ3MgYnkgWWVhciAtLS0tCiAgb3V0cHV0JHRvcDEwQmFyIDwtIHJlbmRlclBsb3QoewogICAgZ2dwbG90KHRvcDEwX3llYXJseSwgYWVzKHggPSBmYWN0b3IocmVsZWFzZWRfeWVhciksIHkgPSBzdHJlYW1zLCBmaWxsID0gc29uZy5hcnRpc3QpKSArCiAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgICAgIGxhYnModGl0bGUgPSAiVG9wIFN0cmVhbWVkIFNvbmcgRm9yIEVhY2ggUmVsZWFzZWQgWWVhcjogVG9wIDEwIFJhbmtpbmcgT3ZlcmFsbCIsCiAgICAgICAgICAgeCA9ICJZZWFyIiwgeSA9ICJTdHJlYW1zIiwgZmlsbCA9ICJTb25nIC0gQXJ0aXN0IikgKwogICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQogIH0pCiAgCiAgIyBUb3AgNSBTb25ncyBwZXIgWWVhciAtLS0tCiAgb3V0cHV0JHRvcFNvbmdzUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIHNlbGVjdGVkX3llYXJfZGF0YSA8LSB5ZWFybHlfdG9wX3NvbmdzICU+JQogICAgICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSBpbnB1dCR5ZWFyKQogICAgCiAgICBzZWxlY3RlZF95ZWFyX2RhdGEkdHJhY2tfbmFtZSA8LSBpY29udihzZWxlY3RlZF95ZWFyX2RhdGEkdHJhY2tfbmFtZSwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIlVURi04Iiwgc3ViID0gIioiKQogICAgCiAgICBnZ3Bsb3Qoc2VsZWN0ZWRfeWVhcl9kYXRhLCBhZXMoeCA9IHJlb3JkZXIodHJhY2tfbmFtZSwgLXN0cmVhbXMpLCB5ID0gc3RyZWFtcywgZmlsbCA9IHNvbmcuYXJ0aXN0KSkgKwogICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIlRvcCA1IFN0cmVhbWVkIFNvbmdzIGluIiwgaW5wdXQkeWVhciksCiAgICAgICAgICAgeCA9ICJTb25nIiwgeSA9ICJTdHJlYW1zIiwgZmlsbCA9ICJTb25nIC0gQXJ0aXN0IikgKwogICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQogIH0pCiAgCiAgIyBSYWRhciBDaGFydCAtLS0tCiAgcmFkYXJfbWF0cml4MyA8LSByZWFjdGl2ZSh7CiAgICByZXEoaW5wdXQkc2VsZWN0ZWRfeWVhcikKICAgIHRvcDNfeWVhciA8LSBzcG90aWZ5ICU+JQogICAgICBmaWx0ZXIocmVsZWFzZWRfeWVhciA9PSBpbnB1dCRzZWxlY3RlZF95ZWFyKSAlPiUKICAgICAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc3RyZWFtcywgbiA9IDMsIHdpdGhfdGllcyA9IEZBTFNFKSAlPiUKICAgICAgZHBseXI6OnJlbmFtZSgKICAgICAgICBkYW5jZWFiaWxpdHkgPSBgZGFuY2VhYmlsaXR5Xy5gLAogICAgICAgIHNwZWVjaGluZXNzID0gYHNwZWVjaGluZXNzXy5gLAogICAgICAgIGVuZXJneSA9IGBlbmVyZ3lfLmAsCiAgICAgICAgYWNvdXN0aWNuZXNzID0gYGFjb3VzdGljbmVzc18uYAogICAgICApICU+JQogICAgICBkcGx5cjo6c2VsZWN0KHNvbmcuYXJ0aXN0LCBicG0sIGRhbmNlYWJpbGl0eSwgc3BlZWNoaW5lc3MsIGVuZXJneSwgYWNvdXN0aWNuZXNzKQogICAgCiAgICByYWRhcl9kYXRhX25vcm0zIDwtIHRvcDNfeWVhciAlPiUKICAgICAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfiByZXNjYWxlKC54LCB0byA9IGMoMCwgMSkpKSkKICAgIAogICAgaWYgKG5yb3cocmFkYXJfZGF0YV9ub3JtMykgPCAxKSByZXR1cm4oTlVMTCkKICAgIAogICAgbWF4X21pbjMgPC0gZGF0YS5mcmFtZSgKICAgICAgYnBtID0gMSwgZGFuY2VhYmlsaXR5ID0gMSwgc3BlZWNoaW5lc3MgPSAxLCBlbmVyZ3kgPSAxLCBhY291c3RpY25lc3MgPSAxLAogICAgICByb3cubmFtZXMgPSBjKCJNYXgiKQogICAgKSAlPiUKICAgICAgYmluZF9yb3dzKGRhdGEuZnJhbWUoCiAgICAgICAgYnBtID0gMCwgZGFuY2VhYmlsaXR5ID0gMCwgc3BlZWNoaW5lc3MgPSAwLCBlbmVyZ3kgPSAwLCBhY291c3RpY25lc3MgPSAwLAogICAgICAgIHJvdy5uYW1lcyA9IGMoIk1pbiIpCiAgICAgICkpCiAgICAKICAgIGJpbmRfcm93cyhtYXhfbWluMywgcmFkYXJfZGF0YV9ub3JtMyAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCJzb25nLmFydGlzdCIpKQogIH0pCiAgCiAgb3V0cHV0JHJhZGFyUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIG1hdHJpeCA8LSByYWRhcl9tYXRyaXgzKCkKICAgIHJlcShtYXRyaXgpCiAgICAKICAgIGNvbG9yc19ib3JkZXIgPC0gcmFpbmJvdyhucm93KG1hdHJpeCkgLSAyKQogICAgY29sb3JzX2luIDwtIGFkanVzdGNvbG9yKGNvbG9yc19ib3JkZXIsIGFscGhhLmYgPSAwLjI1KQogICAgCiAgICBsYXlvdXQobWF0cml4KGMoMSwgMiksIG5yb3cgPSAyKSwgaGVpZ2h0cyA9IGMoNCwgMSkpCiAgICBwYXIobWFyID0gYygyLCAyLCA0LCAyKSkKICAgIHJhZGFyY2hhcnQoCiAgICAgIG1hdHJpeCwKICAgICAgYXhpc3R5cGUgPSAxLAogICAgICBwY29sID0gY29sb3JzX2JvcmRlciwKICAgICAgcGZjb2wgPSBjb2xvcnNfaW4sCiAgICAgIHBsd2QgPSAyLAogICAgICBwbHR5ID0gMSwKICAgICAgY2dsY29sID0gImdyZXkiLAogICAgICBjZ2x0eSA9IDEsCiAgICAgIGF4aXNsYWJjb2wgPSAiZ3JleSIsCiAgICAgIGNheGlzbGFiZWxzID0gc2VxKDAsIDEsIDAuMiksCiAgICAgIGNnbHdkID0gMC44LAogICAgICB2bGNleCA9IDAuOSwKICAgICAgdGl0bGUgPSBwYXN0ZSgiVG9wIDMgU29uZ3MgaW4iLCBpbnB1dCRzZWxlY3RlZF95ZWFyKQogICAgKQogICAgCiAgICBwYXIobWFyID0gYygwLCAwLCAwLCAwKSkKICAgIHBsb3QubmV3KCkKICAgIGxlZ2VuZCgiY2VudGVyIiwgbGVnZW5kID0gcm93bmFtZXMobWF0cml4KVstYygxLCAyKV0sCiAgICAgICAgICAgYnR5ID0gIm4iLCBwY2ggPSAyMCwgY29sID0gY29sb3JzX2JvcmRlciwgdGV4dC5jb2wgPSAiYmxhY2siLCBjZXggPSAwLjkpCiAgfSkKICAKICAjIFBhaXJzIFBsb3QgLS0tLQogIG91dHB1dCRwYWlyc1Bsb3QgPC0gcmVuZGVyUGxvdCh7CiAgICBzZWxlY3RlZF9kYXRhIDwtIGRwbHlyOjpzZWxlY3Qoc3BvdGlmeSwgaW5fc3BvdGlmeV9wbGF5bGlzdHMsIGluX2RlZXplcl9wbGF5bGlzdHMsIGluX2FwcGxlX3BsYXlsaXN0cywgc3RyZWFtcykKICAgIHBhaXJzKHNlbGVjdGVkX2RhdGEsIG1haW4gPSAiUGFpcnMgUGxvdCBvZiBQbGF5bGlzdCBDb3VudHMgYW5kIFN0cmVhbXMiKQogIH0pCiAgCiAgIyBBY3R1YWwgdnMgUHJlZGljdGVkIFBsb3QgLS0tLQogIG91dHB1dCRwcmVkaWN0aW9uUGxvdCA8LSByZW5kZXJQbG90KHsKICAgIGdncGxvdChwbG90X2RhdGEsIGFlcyh4ID0gcHJlZGljdGVkX3N0cmVhbXMsIHkgPSBzdHJlYW1zKSkgKwogICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICAgICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG9yID0gImJsdWUiKSArCiAgICAgIGdlb21fbGluZShhZXMoeSA9IGNvbmZfbG93KSwgY29sb3IgPSAicHVycGxlIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogICAgICBnZW9tX2xpbmUoYWVzKHkgPSBjb25mX2hpZ2gpLCBjb2xvciA9ICJwdXJwbGUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgICAgIGdlb21fbGluZShhZXMoeSA9IHByZWRfbG93KSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZG90dGVkIikgKwogICAgICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkX2hpZ2gpLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgICAgIGxhYnModGl0bGUgPSAiQWN0dWFsIHZzIFByZWRpY3RlZCBTdHJlYW1zIChSXjIgPSAwLjY4KSIsCiAgICAgICAgICAgeCA9ICJQcmVkaWN0ZWQgU3RyZWFtcyIsIHkgPSAiQWN0dWFsIFN0cmVhbXMiKSArCiAgICAgIHRoZW1lX21pbmltYWwoKQogIH0pCiAgCiAgIyBQcmVkaWN0IFN0cmVhbXMgLS0tLQogIG9ic2VydmVFdmVudChpbnB1dCRwcmVkaWN0LCB7CiAgICBuZXdfaW5wdXQgPC0gZGF0YS5mcmFtZSgKICAgICAgaW5fc3BvdGlmeV9wbGF5bGlzdHMgPSBpbnB1dCRzcG90aWZ5LAogICAgICBpbl9kZWV6ZXJfcGxheWxpc3RzID0gaW5wdXQkZGVlemVyLAogICAgICBpbl9hcHBsZV9wbGF5bGlzdHMgPSBpbnB1dCRhcHBsZQogICAgKQogICAgCiAgICBwcmVkaWN0ZWQgPC0gcHJlZGljdChjdl9tb2RlbCwgbmV3ZGF0YSA9IG5ld19pbnB1dCkKICAgIAogICAgb3V0cHV0JHByZWRpY3Rpb24gPC0gcmVuZGVyVGV4dCh7CiAgICAgIGZvcm1hdChyb3VuZChwcmVkaWN0ZWQsIDApLCBiaWcubWFyayA9ICIsIikKICAgIH0pCiAgfSkKfQoKIyBSdW4gdGhlIEFwcApzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAoK